Skip to content

Latest commit

 

History

History
211 lines (167 loc) · 8.76 KB

Boardy Modularization.md

File metadata and controls

211 lines (167 loc) · 8.76 KB

Boardy Modularization

IOInterface

Boardy is a micro-service architecture, it promotes flexibility, interchangeability, independent deployment. Data consistency might be a problem need to care. An approach, data between micro-services is transferable to primitive data types, things that every micro-service knows well. Output of micro-service A should be encoded to Swift.Data, after that the raw data will be passed to micro-service B, the micro-service then decodes data to expected pure object and make a decision to reject or accept. This approach is popular and easy to implement. But Swift appreciates type safety and compile time check. So encoding & decoding may cause data type mismatch or when a micro-service changes data, other services interacting with it are not aware of the changes at compile time and easily cause runtime errors, difficult to trace. Another approach is using Shared Interface. By that way, micro-service A defines its interface which contains Input type to other services call to A, Output type to other services handle callback and some template methods to interact with A type safety. The interface called Input Output Interface or IOInterface for short.

A IOInterface contains 2 flies:

  • InOut.swift where defines Input/ Output types and other data structures (You should modify these to your expected types).
  • IOInterface.swift is generated by template (you shouldn't edit this file), it contains some extension methods to other services call using Boardy.

Each of micro-service has a IOInterface. Boardy Modularization provides templates to create IOInterface easily.

ModulePlugin

Each application has a main component where all things integrated with each other. Main Component will soon become cumbersome and difficult to manage as the application expands. Boardy provides a plugin mechanism for the integration of micro-service modules. Using the plugin module, adding or replacing a module is extremely simple, make your application become scalable.

Boardy Module Plugin is a interface to enable extending MainComponent. By that way, MainComponent expose some open functions then module plugins can use these functions to register services which they provide.

A Module Plugin looks like:

import Boardy
import Foundation
import PaymentIO

public struct PaymentModulePlugin: ModulePlugin {
    public let service: PaymentModulePlugin.ServiceType

    public init(service: PaymentModulePlugin.ServiceType) {
        self.service = service
    }

    public func apply(for main: MainComponent) {
        let mainProducer = main.producer

        switch service {
        case .default:
            mainProducer.registerBoard(identifier) { [unowned mainProducer] identifier in
                RootBoard(
                    identifier: identifier,
                    producer: BoardProducer(
                        externalProducer: mainProducer,
                        registrationsBuilder: { producer in
                            // <#registration code#>
                        }
                    )
                )
            }
        }
    }

    public var identifier: BoardID {
        switch service {
        case let .default(identifier):
            return identifier
        }
    }

    /// Each service is equivalent to one entry point
    public enum ServiceType {
        case `default`(BoardID)
    }
}

extension PaymentModulePlugin {
    public static var bundledPlugins: [ModulePlugin] {
        return [
            PaymentModulePlugin(service: .default(PaymentID.default)),
        ]
    }
}

👉 You can use templates (will be introduced below) to create a Module Plugin for your module. Then you can edit func apply(for main: MainComponent) to register a Board as entry point of your module. When use IOInterface to activate YourModule, this Board will be activated corresponding.

⭐️ Your Module might have multiple entry points, that is when you should define more ServiceType and provide correct destination Board in func apply(for main: MainComponent).

⭐️ In addition to defining the entry point, ModulePlugin is the best place to allocate internal instances of your module and resolve dependencies to external services.

Module Plugin will be integrated into the app through the PluginLauncher, which will be presented in the following section.

Templates

For the purpose of increasing efficiency, Boardy Modularization provides a series of templates that make creating micro-services convenient and simple.

  • Boardy Xcode templates for micro-service creation.
    • Boardy for creating a Boardy micro-service with UI, Controller, Board and IOInterface.
    • EmptyBoard for creating a Boardy micro-service with Board and IOInterface.
    • IOInterface for creating public IOInterface only.
    • ModuleIntegration for creating a ModulePlugin implementation for a module.
    • TaskBoard for creating micro-service without UI, task only.
    • BlockTask & BarrierBoard for creating other TaskBoard types.

  • Boardy Modularization template is Cocoapods template for module using Boardy Module Plugin creation. When you use this template for creating new module, you will get 2 podspecs corresponding to 2 modules, one is Interface only (IO module), one is implementation module.
    • ModuleIO contains IOInterface only of Implementation Module
    • Implementation Module contains a ModulePlugin, a default RootBoard as module entrance and some extensions, placeholder classes.

Example notifications module

Install templates

We provide some scripts to install these templates and some other tools.

git clone https://github.com/ifsolution/father-scripts.git
  • Install Boardy Xcode templates
sh father-scripts/install-template.sh
  • Create a new module use Boardy Modularization template

If you using local development pods, please follow below steps

# 1. Create submodules folder to contain your submodules if needed
mkdir submodules
# 2. Create folder for your module
cd submodules
mkdir your-module
# 3. Create initial source code using template
cd your-module
sh ../../father-scripts/init-module.sh YourModuleName
# 4. Add Your Module to Podfile
pod "YourModule", :path => "submodules/your-module"
pod "YourModuleIO", :path => "submodules/your-module"
# 5. Install pods
pod install

Set up App Launcher

  • Load all modules into PluginLauncher in AppDelegate
extension AppDelegate {
    func loadPlugins() {
        PluginLauncher.with(options: .default)
            .install(plugins: AuthDataModulePlugin.bundledPlugins)
            .install(plugins: ProductDataModulePlugin.bundledPlugins)
            .install(plugin: AuthModulePlugin())
            .install(plugin: HomeModulePlugin())
            .install(plugin: CategoriesModulePlugin())
            .install(plugins: NotificationsModulePlugin.bundledPlugins)
            .install(plugin: ShoppingCartModulePlugin())
            .install(plugin: AccountModulePlugin())
            .install(plugin: ProductModulePlugin())
            .install(plugins: ShoppingCartDataModulePlugin.bundledPlugins)
            .install(plugins: ShoppingAddressDataModulePlugin.bundledPlugins)
            .install(plugins: OrderServiceModulePlugin.bundledPlugins)
            .install(plugin: RootContainerModulePlugin())
            .install(plugin: ShoppingAddressModulePlugin())
            .install(plugins: ProductDetailModulePlugin.bundledPlugins)
            .install(plugins: ShoppingNotificationDataModulePlugin.bundledPlugins)
            .initialize()
    }
}
  • Add method to launch application and start first module as root
extension AppDelegate {
    func launch() {
        if let window = window {
            PluginLauncher.shared.launch(on: window) { mainboard in
               // Use IOInterface to call to root module
                mainboard.ioRootContainer().activation.activate()
            }
        }
    }
}
  • Finish your app
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 1. Load all module plugins
        loadPlugins()
        
        // 2. Launch root module
        launch()

        return true
    }
}

From Boardy with ♥️