Appearance
Reactive communication between plugins
Existing PlatformHub API [Alpha.8]
PluginProtocol
swift
init(messageSender: MessageSending, configurationFiles: [String: String])
var policy: Policy { get }
func publisher(forEvent event: Message) -> AnyPublisher<JSONObject, Never>
func subscribe(toPublisher publisher: AnyPublisher<JSONObject, Never>, forEvent event: Message)
func onReceive(command message: Message, payload: JSONObject?) -> AnyPublisher<Result<JSONObject, Error>, PluginError>
func onReceive(viewControllerQuery message: Message, payload: JSONObject?) -> Result<UIViewController, PluginError>
func onReceive(query message: Message, payload: JSONObject?) -> Result<JSONObject?, PluginError>MessageSending
swift
func send(command: Message, payload: JSONObject?, sender: PluginProtocol) -> AnyPublisher<Result<JSONObject, Error>, PluginError>
func send(viewControllerQuery: Message, payload: JSONObject?, sender: PluginProtocol) -> Result<UIViewController, PluginError>
func send(query: Message, payload: JSONObject?, sender: PluginProtocol) -> Result<JSONObject?, PluginError>Policy
json
{
"name": "PluginName",
"send": {
"commands": [],
"queries": [],
"viewControllerQueries": []
},
"receive": {
"commands": [],
"queries": [],
"viewControllerQueries": []
},
"publish": {
"events": []
},
"subscribe": {
"events": []
}
}Success sequence diagram

Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Network
rect rgb(235, 235, 235)
Note left of JourneyPlugin: Current solution
JourneyPlugin->>DomainModel: command(getInfo)
DomainModel->>DomainModel: InfoPublisher.init()
DomainModel->>JourneyPlugin: InfoPublisher:AnyPublisher<Result<JSONObject, Error>, PluginError>
JourneyPlugin->>JourneyPlugin: subscribe(infoPublisher)
DomainModel->>Network: fetchInfo
Network-->>DomainModel: info1
DomainModel-->>JourneyPlugin:InfoPublisher-> success(JSONObject(info1))
JourneyPlugin->>JourneyPlugin: updateUI(info)
end
rect rgb(235, 235, 235)
JourneyPlugin->>DomainModel: query(refreshInfo)
DomainModel->>JourneyPlugin: success(nil): Result<JSONObject?, PluginError>
DomainModel->>Network: fetchInfo
Network-->>DomainModel: info2
DomainModel-->>JourneyPlugin: InfoPublisher-> success(JSONObject(info2))
JourneyPlugin->>JourneyPlugin: updateUI(info2)
end
```
Error in command
For example there is an issue with the payload sent i.e. there is a missing mandatory field. 
Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Network
rect rgb(235, 235, 235)
Note left of JourneyPlugin: Current solution
JourneyPlugin->>DomainModel: command(getInfo)
DomainModel->>DomainModel: InfoPublisher.init()
DomainModel->>JourneyPlugin: InfoPublisher:AnyPublisher<Result<JSONObject, Error>, PluginError>
JourneyPlugin->>JourneyPlugin: subscribe(infoPublisher)
DomainModel-->>JourneyPlugin: infoPublisher(Fail(error))
end
```
Error in network
The async operation i.e. a network request comes back with an error i.e. HTTP error code 500 
Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Network
rect rgb(235, 235, 235)
Note left of JourneyPlugin: Current solution
JourneyPlugin->>DomainModel: command(getInfo)
DomainModel->>DomainModel: InfoPublisher.init()
DomainModel->>JourneyPlugin: InfoPublisher:AnyPublisher<Result<JSONObject, Error>, PluginError>
JourneyPlugin->>JourneyPlugin: subscribe(infoPublisher)
DomainModel->>Network: fetchInfo
Network-->>DomainModel: error
DomainModel-->>JourneyPlugin:InfoPublisher-> failure(error)
JourneyPlugin->>JourneyPlugin: updateUI(error)
end
```
Error in query
For example there is an issue with the payload sent i.e. there is a missing mandatory field. 
Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Network
rect rgb(235, 235, 235)
Note left of JourneyPlugin: Current solution
JourneyPlugin->>DomainModel: command(getInfo)
DomainModel->>DomainModel: InfoPublisher.init()
DomainModel->>JourneyPlugin: InfoPublisher:AnyPublisher<Result<JSONObject, Error>, PluginError>
JourneyPlugin->>JourneyPlugin: subscribe(infoPublisher)
DomainModel->>Network: fetchInfo
Network-->>DomainModel: error
DomainModel-->>JourneyPlugin:InfoPublisher-> success(JSONObject(info1))
JourneyPlugin->>JourneyPlugin: updateUI(info)
end
rect rgb(235, 235, 235)
JourneyPlugin->>DomainModel: query(refreshInfo)
DomainModel->>JourneyPlugin: failure(PluginError)
end
```
New API in Alpha.9
Summary
- Events are not triggered on app launch only but also on demand
- Commands are fire and forget, meaning they are a one way communication
- Methods can throw as a result of an error in processing the message i.e. wrong payload attached
- Motivations:
- Enforce a strictly reactive approach or our apps and plugins via PH's interface
- Specialization over versatility: Making the intent of each message type clear, resulting in easier communication between plugins in a reactive approach.
The changes in the interfaces will be as follows:
Policy
json
{
"name": "PluginName",
"send": {
"commands": [],
"queries": [],
"requests": [],
"viewControllerQueries": []
},
"receive": {
"commands": [],
"queries": [],
"requests": [],
"viewControllerQueries": []
},
"publish": {
"events": []
},
"subscribe": {
"events": []
},
"subscribeAtAppLaunch": {
"events": []
}
}PluginProtocol
swift
func publisher(forEvent event: Message, payload: JSONObject?) throws -> AnyPublisher<EventOutput, Never>
func onReceive(command: Message, payload: JSONObject?) throws
func onReceive(query: Message, payload: JSONObject?) throws -> JSONObject
func onReceive(viewQuery: Message, payload: JSONObject?) throws -> UIViewController
func onReceive(viewQuery: Message, payload: JSONObject?) throws -> any ViewMessageSending
swift
func publisher(forEvent event: Message, payload: JSONObject?, subscriber: PluginProtocol)
throws -> AnyPublisher<EventOutput, Never>
func send(command: Message, payload: JSONObject?, sender: PluginProtocol) throws
func send(query: Message, payload: JSONObject?, sender: PluginProtocol) throws ->JSONObject
func send(viewQuery: Message, payload: JSONObject?, sender: PluginProtocol) throws ->UIViewController
func send(viewQuery: Message, payload: JSONObject?, sender: PluginProtocol) throws -> anyViewError in getPublisher message
For example there is an issue with the payload sent i.e. there is a missing mandatory field. An error is thrown synchronously. 
Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Server
rect rgb(191, 223, 255)
JourneyPlugin->>DomainModel: getPublisher(forEvent:info, payload?) throws
DomainModel->>JourneyPlugin: throw(Error)
end
```
Error in command message
For example there is an issue with the payload sent i.e. there is a missing mandatory field. An error is thrown synchronously. 
Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Server
rect rgb(191, 223, 255)
JourneyPlugin->>DomainModel: getPublisher(forEvent:info, payload?) throws
DomainModel->>DomainModel: InfoPublisher.init()
DomainModel->>JourneyPlugin: InfoPublisher: AnyPublisher<Result<JSONObject, Error>, Never>
JourneyPlugin->>JourneyPlugin: subscribe(infoPublisher)
end
rect rgb(191, 223, 255)
JourneyPlugin->>DomainModel: command(refreshInfo, id: `abc`) throws
DomainModel->>JourneyPlugin: throw(error)
end
```
Error in the server
The async operation i.e. a network request comes back with an error i.e. HTTP error code 500 
Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Server
rect rgb(191, 223, 255)
JourneyPlugin->>DomainModel: getPublisher(forEvent:info, payload?) throws
DomainModel->>DomainModel: InfoPublisher.init()
DomainModel->>JourneyPlugin: InfoPublisher: AnyPublisher<Result<JSONObject, Error>, Never>
JourneyPlugin->>JourneyPlugin: subscribe(infoPublisher)
end
rect rgb(191, 223, 255)
JourneyPlugin->>DomainModel: command(refreshInfo, id: `abc`) throws
DomainModel->>Server: fetchInfo
Server-->>DomainModel: error
DomainModel-->>JourneyPlugin: InfoPublisher-> EventOutput(Result(error), correlationId: `abc`)
JourneyPlugin->>JourneyPlugin: filterMessage(correlationId)
JourneyPlugin->>JourneyPlugin: updateUI(error)
end
```
Success

Mermaid code
```mermaid
sequenceDiagram
participant JourneyPlugin
participant DomainModel
participant Server
rect rgb(191, 223, 255)
JourneyPlugin->>DomainModel: getPublisher(forEvent:info, payload?) throws
DomainModel->>DomainModel: InfoPublisher.init()
DomainModel->>JourneyPlugin: InfoPublisher: AnyPublisher, Never>
JourneyPlugin->>JourneyPlugin: subscribe(infoPublisher)
end
rect rgb(191, 223, 255)
JourneyPlugin->>DomainModel: command(refreshInfo, id: `abc`) throws
DomainModel->>Server: fetchInfo
Server-->>DomainModel: info
DomainModel-->>JourneyPlugin: InfoPublisher-> EventOutput(Result(success: JSONObject(info)), correlationId: `abc`)
JourneyPlugin->>JourneyPlugin: updateUI(info)
end
```
Alternatives considered & discarded for Alpha.9
Below we walk through the different implementation alternatives considered and why they were eventually discarded.
Discarded option 1
Introduce a Request message which would be an async message that will eventually return a result. We can think about it as a sophisticated completion block.
PluginProtocol
func onReceive(request message: Message, payload: JSONObject?) async throws -> Result<JSONObject, Error>
MessageSending
func send(request: Message, payload: JSONObject?, sender: PluginProtocol) async throws -> Result<JSONObject, Error>
Why we discarded it?
We would allow developers to use a not strictly reactive interface. Although it would make it easier for them to start producing code in the long term we would be only generating tech debt and slowing down our transition to a fully reactive architecture.
Discarded option 2
Similar to the option above but instead of adding a new Request message we would be repurposing the existing Query messages by turning them into async calls. Only sync message in PH would be NavigationQueries as for navigation would be much handier calls to be sync. Also ViewControllers are not data that can be observed in a reactive way.
PluginProtocol
func onReceive(query message: Message, payload: JSONObject?) async -> Result<JSONObject, Error>
MessageSending
func send(query: Message, payload: JSONObject?, sender: PluginProtocol) async -> Result<JSONObject, Error>
Why we discarded it?
As in the previous point we would allow developers not to follow a reactive architecture. By turning Queries async, we would violate the principle of a Query being a message that just inspect a value, since it could be used to fetch information that would require API calls or heavy computation logic.
Discarded option 3
Command is still a 2 way async communication where we will eventually receive a single response via a Future.
- Commands will be intended for:
- Triggering a one way message with a future that will just return a payload containing a boolean indicating success or a failure with error
- Fetching async data outside the event driven paradigm as we already have. This would be a substitute for traditional completion blocks or asyn/await calls
PluginProtocol
func onReceive(command message: Message, payload: JSONObject?) -> Future<Result<JSONObject, Error>, PluginError>
MessageSending
func send(command: Message, payload: JSONObject?, sender: PluginProtocol) -> Future<Result<JSONObject, Error>, PluginError>
Why we discarded it?
- Potential confusion on how we are intending to use Commands: as a one way "action" or as a async method to retrieve data
- Confusing interface if it only returns a success or error but not actual data
- As in the previous options we would allow developers not to follow a reactive architecture