Part 3: Putting a Domain in Place

For the most part of this exercise, the Domain consisted of two Entities: Box and Item. It wasn’t a domain model worth talking about. There were data containers without any behavior at all. There were no business rules except managing Items in Boxes.

This changed a bit when I realized I had mixed Domain Service and Application Service into a single object. The resulting ProvisioningService in the domain creates Entities, adds them to the repository, and notifies interested parties of the event.

Notifications are useful for auto-updating the view as I mentioned in the last part already. They are useful for populating an event store, too: persist the events themselves instead of Entity snapshots to replay changes and thus synchronize events across multiple clients, for example. This is called Event Sourcing and replaces traditional database models to persist Entity states. Digging into this goes way beyond the scope of this book, but I’m eager to try it in the future (hint, hint).

Introducing Events in Place of Notifications

In the last part, I ended up using NotificationCenter to send events. Notifications are easy to use and Foundation provides objects that are well-known. I don’t like how working with a notification’s userInfo dictionary gets in the way in Swift, though. Objective-C was very lenient when you used dictionaries. That introduced a new source of bugs, but if you enjoy dynamic typing, it worked very well. Swift seems to favor new paradigms that enforce strong typing.

Swift Is Sticking Your Head Right at the Problem

In Objective-C, I’d send and access the “Box was created” event info like this:

// Sending
NSDictionary *userInfo = @[@"boxId": @(boxId.identifier)];
[notificationCenter postNotificationName:kBoxProvisioned,
                                  object:self,
                                userInfo:userInfo];

// Receiving
int64_t identifier = notification.userInfo["boxId"].longLongValue;
[BoxId boxIdWithIdentifier:identifier];

Swift 3 allows sending notifications with with number value types directly. We don’t have to wrap them in NSNumber anymore. Still, the force-unwrapping and the forced cast make me nervous in Swift because they point out a brittle piece of code:

// Sending
let userInfo = ["boxId" : boxId.identifier]
notificationCenter.post(name: kBoxProvisioned, 
    object: self, userInfo: userInfo)

// Receiving
let boxInfo = notification.userInfo!["boxId"] as! NSNumber
let identifier = boxInfo.int64Value
let boxId = BoxId(identifier: identifier)

I settled for sending IDs only because putting ID and title makes things complicated for the “Item was created” event. There, I’d have to use nested dictionaries. A JSON representation would look like this:

JSON representation of the event data
{
    box: {
        id: ...
    }
    item: {
        id: ...
        title: "the title"
    }
}

Accessing nested dictionaries in Swift is even worse, though, so I settled with supplying two IDs only. On the downside, every client now has to fetch data from the repository to do anything with the event. That’s nuts.

The relative pain I experience with Swift here highlights the problems Objective-C simply assumed we’d take care of: there could be no userInfo at all, there could be no value for a given key, and there could be a different kind of value than you expect.

It’s always a bad idea to simply assume that the event publisher provided valid data in dictionaries. Force-unwrapping and force-casting will accidentally break sooner or later. What if you change dictionary keys in the sending code but forgot to update all client sites? Defining constants remedies the problem a bit. But the structure of a dictionary is always opaque, and if you change it, you have to change multiple places in your code. It’s a good code heuristic to look for changes that propagate through your code base. If you have to touch more than 1 place to perform a change, that’s an indicator of worse than optimal encapsulation: these co-variant parts in your app depend on one another but don’t show their dependency explicitly.

So you have to perform sanity checks to catch invalid events anyway, in Objective-C just as much as in Swift.

Using real event objects will work wonders. Serializing them into dictionaries and de-serializing userInfo into events will encapsulate the sanity checks and provide usable interfaces tailored to each event’s use. There’s only one place you need to worry about if you want to change the nature of an event.

Event Value Types

An event should be a value type, and thus a struct. It assembles a userInfo dictionary. For NotificationCenter convenience, it also assembles a Notification object:

Domain Event serializing itself into userInfo dictionary
 1 // Provide a typealias for brevity and readability
 2 public typealias UserInfo = [AnyHashable : Any]
 3 
 4 public struct BoxProvisionedEvent: DomainEvent {
 5 
 6     public static let eventName = Notification.Name(
 7         rawValue: "Box Provisioned Event")
 8 
 9     public let boxId: BoxId
10     public let title: String
11 
12     public init(boxId: BoxId, title: String) {
13         self.boxId = boxId
14         self.title = title
15     }
16 
17     public init(userInfo: UserInfo) {
18         let boxIdentfier = userInfo["id"] as! IntegerId
19         let title = userInfo["title"] as! String
20         self.init(boxId: BoxId(boxIdentfier), title: title)
21     }
22 
23     public func userInfo() -> UserInfo {
24         return [
25             "id" : boxId.identifier,
26             "title" : title
27         ]
28     }
29 }

The underlying protocol is really simple:

1 public protocol DomainEvent {
2     static var eventName: Notification.Name { get }
3 
4     init(userInfo: UserInfo)
5     func userInfo() -> UserInfo
6 }

When publishing an event, these properties can be used to convert to a Notification. I used to use a free notification(_:) function for that:

func notification<T: DomainEvent>(event: T) -> Notification {
    return Notification(name: T.eventName, object: nil, 
        userInfo: event.userInfo())
}

Now with protocol extensions, the conversion can be coupled more closely to the DomainEvent protocol, getting rid of the free function:

extension DomainEvent {
    public func notification() -> Notification {
        return Notification(
            name: type(of: self).eventName,
            object: nil,
            userInfo: self.userInfo())
    }

    func post(notificationCenter: NotificationCenter) {
        notificationCenter.post(self.notification())
    }
}

See the convenience method post(notificationCenter:)? That can come in handy when testing the actual sending of an event if you override it in the tests.

Now BoxProvisionedEvent wraps the Notification in something more meaningful to the rest of the app. It also provides convenient accessors to its data, the ID and title of the newly created box. That’s good for slimming-down the subscriber: no need to query the repository for additional data.

There’s a DomainEventPublisher which takes care of the actual event dispatch. We’ll have a look at that in a moment. With all these changes in place, the DisplayBoxesAndItems Application Service now does no more than this:

 1 class DisplayBoxesAndItems {
 2     var publisher: DomainEventPublisher! {
 3         return DomainEventPublisher.sharedInstance
 4     }
 5     
 6     // ...
 7     
 8     func subscribe() {
 9         let mainQueue = OperationQueue.mainQueue()
10     
11         boxProvisioningObserver = publisher.subscribe(
12             BoxProvisionedEvent.self, queue: mainQueue) {
13                 [weak self] (event: BoxProvisionedEvent!) in
14                 
15                 let boxData = BoxData(boxId: event.boxId, title: event.title)
16                 self?.consumeBox(boxData)
17         }
18         
19         // ...
20     }
21     
22     func consumeBox(boxData: BoxData) {
23         consumer?.consume(boxData)
24     }
25     
26     // ...
27 }

The subscribe method is interesting. Thanks to Swift generics, I can specify an event type using TheClassName.self (the equivalent to [TheClassName class] in Objective-C) and pipe it through to the specified block to easily access the values.

The conversion of Notification to the appropriate domain event takes place in the DomainEventPublisher:

 1 func subscribe<T: DomainEvent>(
 2     _ eventKind: T.Type, 
 3     queue: OperationQueue, 
 4     usingBlock block: (T) -> Void) 
 5     -> DomainEventSubscription {
 6     
 7     let eventName: String = T.eventName
 8     let observer = notificationCenter.addObserver(forName: eventName, 
 9         object: nil, queue: queue) {
10         notification in
11         
12         let userInfo = notification.userInfo!
13         let event: T = T(userInfo: userInfo)
14         block(event)
15     }
16     
17     return DomainEventSubscription(observer: observer, eventPublisher: self)
18 }

It takes some getting used to Swift to read this well. I’ll walk you through it.

Let’s stick to the client code from above and see what subscribing to BoxProvisionedEvents does:

  • The type of the eventKind argument should the type (not instance!) of a descendant of DomainEvent. That’s what BoxProvisionedEvent.self is. You don’t pass in an actual event, but it’s class (or “type”). Interestingly, this value is of no use but to set T, the generics type placeholder.
  • The block (line 3) yields an event object of type T (which becomes an instance of BoxProvisionedEvent, for example)
  • The eventName (line 4) will be, in this example case, Box Provisioned Event. DomainEvents have a property called eventName to return a string which becomes the notification name.
  • The actual observer is a wrapper around the block specified by the client. The wrapper creates an event of type T. All DomainEvents must provide the deserializing initializer init(userInfo: [Hashable : Any]), and so does T.

When a BoxProvisioned event is published, it is transformed into a Notification. The notification is posted as usual. The wrapper around the client’s subscribing block receives the notification, de-serializes a BoxProvisioned event again, and provides this to the client.

DomainEventSubscription is a wrapper around the observer instances NotificationCenter produces. This wrapper unsubscribes upon deinit automatically, so all you have to do is store it in an attribute which gets nilled-out at some point.

It took some trial and error to get there, but it works pretty well.3