Search code examples
callbackarchitectureexternalclean-architecturehexagonal-architecture

Is there an elegant solution for handling callbacks from external devices in clean architecture?


I am building a complex system, based on clean architecture (https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html), using many external components, for example a payment terminal. The mentioned payment terminal has a library which contains basic functions, including a place to pass a callback, so that the terminal can inform about the progress of the transaction, e.g. (insert pin or remove the payment card).

Let's consider a scenario:

  1. User presses the button "Perform transaction", using some kind of adapter system notices that user wants to perform transaction.
  2. System using other kind of adapter calls external API and tells it "perform transaction".
  3. Now we would like to inform user about the progress... via external API callback.

in order to use a callback API, one or more of our application classes must implement the callback method(s), and must therefore conform to some abstraction defined by the API’s provider. So our classes must depend on the API. Which means that the API can’t be easily mocked or stubbed. We have to treat our callback objects as being part of the adapter for that API, and test the rest of the application by mocking or stubbing them.

On one hand, I would not want to make the architecture dependent on an external API, but creating new entities specifically to handle API events seems over-engineered to me.


Solution

  • I would create a progress interface in the use case layer, since it reports progress of the use case.

    Then I would implement a progress presenter and pass it to the use case when it is called from the controller.

    The use case can then pass the progress to the payment terminal adapter through it's interface.

    +------------+     +-------------------+  <<create>>  +------------+
    | View Model | <-- | ProgressPresenter | <----------  | Controller |
    +------------+     +---------+---------+              +------------+
                                 |                              |
    UI                           |                              |
    =============================|==============================|==========
    Use Case                     V                              V
                           +----------+                    +----------+
                           | Progress |                    | Use Case |
                           +----------+                    +----------+
                                 ^                              |
                                 |                              V
                                 |                     +-----------------+
                                 |                     | PaymentTerminal |   
                                 |                     +-----------------+
                                 |                              | 
    =============================|==============================|==========
    External                     |                              V
                                 |                  +------------------------+
                                 +------------------| PaymentTerminalAdapter |
                                                    +------------------------+
    

    In a rich client application the ViewModel is usually connected to the UI component via an observer relationship and will cause the UI to update when the ViewModel changes. In an web application you either have to do this through some kind of websocket or save the progress in some kind of storage (session or database) and let the client poll the value.