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.
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.
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.
We provide some scripts to install these templates and some other tools.
- Clone scripts repo to your project root folder
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
- Load all modules into
PluginLauncher
inAppDelegate
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
♥️