Skip to content

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

Current implementation async success

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. Current command error

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 Current network error

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. Current query error

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 View

MessageSending

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 -> anyView

Error 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. Get publisher error

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. Command error

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 Network error

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

Async 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