Domain Events in iOS with RxSwift
All letgo backend microservices are integrated using Domain Events. In the iOS team we’ve adopted the same approach. Our UI reacts to changes that are published into an event bus which is contained in the service. This means we make the screens subscribe to model changes with minimal coupling. We‘ve implemented it with the help of the reactive programming framework RxSwift, and the concept applies to any other reactive framework.
This article discusses how to consistently refresh multiple screens that represent a model it updates. It briefly exposes some possible solutions and explains how the iOS engineering team at letgo has decided to tackle this problem: using Domain Events.
Problem: Update multiple screens with a model update
The letgo app is a secondhand marketplace. As a user, you list an item, chat with other users, arrange a meeting to sell your item and make some cash. In this process the item appears (or may appear) in many screens: main feed, chat list and conversation details, user profile… When users mark items as sold, they would expect all screens displaying that item to update with the new sold state.
How can this problem be solved?
- Delegation pattern across screens. Problems: Tight coupling of screens and we would only have 1–1 communication.
NSNotificationCenter
to notify from the service to the screens. It would make the screens loosely coupled and broadcast to all subscribers. Problem: If we are passing objects via notifications then casting is required. This results in having a weak implementation: if we change the notified types, we won’t notice when we compile since it’s not a strongly typed solution.
Have any other ideas on how to solve this? If you do, please leave a comment :)
What’s a Domain Event?
The Domain Event definition in the .NET Microservices guides:
A domain event is, logically, something that happened in a particular domain, and something you want other parts of the same domain (in-process) to be aware of and potentially react to.
The idea is that a system publishes a message (Domain Event) when something happens. The message is published asynchronously in a bus which other systems listen and react to when required. Domain Events are part of the building blocks of Domain-Driven Design (DDD).
A Domain Event is a DTO that represents the event that just happened.
Domain Events with RxSwift
The domain events solution we designed so far:
- The scope is the service. We understand the service layer as the layer exposed to the presentation layer, or other services, that uses and coordinates repositories, where the implementation of the operations happens.
- Domain events are modelled as an
enum
that describes the actions that might happen to a model instance (or its identifier) - The events are exposed through a RxSwift
Observable
variable - Sometimes, we provide some helper methods to filter a given object, collection of objects or specific event types
Here’s a simple example: a UITabBarController
-based iOS app which contains 3 item lists from which we can access a screen with more details about the item. The list controller can generate random items when pressing the “+” button. The item detail controller randomly modifies the item when pressing the “Edit” button. The proof of concept is that when editing an item in a detail screen all other screens update consequently.
ListingService
defines (partially) CRUD actions interface to work with a Listing
type. These actions have some input parameters and return the result execution in a completion block. The Result
wraps either a Listing
or an error. In events
we publish the events that happened after executing the service actions. Every interested component with access to the service will be able to subscribe to the Listing
domain events and react consequently.
ListingServiceEvent
enumerates the Listing
actions as events that happened with theListing
associated type.
filteredEvents(listing:)
method is just a helper method that filters the events Observable
with the given Listing
.
In LocalListingService
, aListingService
implementation, we publish the event for each CRUD method ineventsPublishSubject
which is exposed via events
.
ListingListViewController
is the controller that shows the list of listings. It contains a ListListView
as content view. ListingListViewController
has a ListingService
injected in the controller constructor init(service:)
. In viewDidLoad
the controller subscribes to the service domain events. When an event is received the view is notified to create, update or delete the listing. Then, the view updates the UITableView
. Every time a new listing is created, updated or deleted anywhere in the app our table view is updated.
You will find a fully working example of this implementation in the following repository:
Conclusions
A domain events approach can help us build reactive interfaces with minimal coupling. The view (or view model, presenter… depending on the presentation architecture) has a bounded context and limited responsibility as only knows about itself and the service/s domain events. Therefore, the solution improves the view testability.
Enumerations are great to model the event types and thanks to the fact that Swift is a strongly typed language, the implementation turns out to be quite maintainable.
It’s important not to forget that domain events model something that happened. Therefore using domain events to ask for actions will be a wrong usage. Also, the layer where domain event emitters are hosted should be the one in charge of the business logic for the domain types given. It’s simpler if we focus our design on the domain model.
Other possible side effects that we can trigger when receiving domain events might be:
- service dependencies: make a service react to others’ updates
- analytics / logging: track events when receiving service updates
Also, this approach can be useful as a transition to a unidirectional data flow architecture. We could consider that a domain event partially models the state update in a Redux-like architecture.