Friday, 11 July 2014

Implementing a DDD project in Clojure - 1

In the last couple of weeks I've been occupying myself with porting the Blackjack Java project to Clojure. I've been curious whether DDD concepts could be reused in a functional language without sacrificing idiomaticity. There is still a long way to go, but the walking (limping) skeleton is ready. Check it on github. In the followings I make an attempt to summarize my experiences.

1. Aggregates

First of all Clojure strongly encourages working with immutable data structures. My naive assumption had been that an OO aggregate can be simply substituted with a data structure accompanied by a handful of functions operating on it. This is only partially true. Though I've found state mutation easily replaceable with creating a new data structure (Clojure's persistent data structures make it performant, and its very effective data manipulation functions easy) as the output of the applied function, but I'm not convinced about how invariants should be preserved in the absence of an entity with mutable state. To some degree it can be achieved by :pre and :post constraints on the individual functions, but I haven't really dived into it.

2. Domain events

Related to the previous point, since in OO aggregate roots are to publish domain events. Initially I implemented my "aggregates" the same way, the functions operating on them published events as a side-effect. But eventually I've realized it's very non-FP, so instead of invoking a side-effect, I changed these functions to return not only the updated data structure (the new state of the "aggregate"), but the event-list as well. Then the new "state" is updated in the DB and the events are sent out. This solution (which could be applied in OO as well) eliminates the need of thread-local/event store-flushing magic I resorted to in the java version. I am more than satisfied with this, it's not only idiomatic, but makes things so much simpler.

3. AOP for locking

This is quite pleasing also. Instead of aspects and annotations, it's a 5-(short)-line macro.

4. Layering

The most interesting part. In the very few books mentioning the large-scale structure of FP-programs I've stumbled across the notion of onion-layering, not unlike that of DDD or Hexagonal Architecture. We should strive to confine the mutable states to the outer layers, keeping the core completely functional. Unfortunately in OO-DDD, the "core" is the Domain, which does have logic with side-effects. Repository interfaces and Domain Services as front-doors to ACLs are very much part of the Domain, and are there to enable the Domain to interact with the external world (writing to DB, sending messages, calling web services, ...).
So quite early it became clear that the layering in FP-programs requires some changes in the OO-way of thinking.

4.1 Hexagonal Architecture

If we forget about DDD temporarily and reach back to HA (which I think is one of the foundations of the former), some parallels with the FP-version of onion-layering are obvious. In HA, the Adapters are outside of the core and the Ports are the joints that bind the Adapters to the core. Even an FP-program must interact with the external world (otherwise what's the point of writing it), so HA, with its explicitly designated areas of external interaction seems to be a natural fit.

4.2 Application layer

Having a clean facade for an application has nothing to do with OO or FP, so keeping this layer of DDD seemed ok. In addition to the normal DDD application layer responsibilities, such as locks, transactions and orchestration, I decided all calls to secondary (driving) ports should happen here (calls to a remote Bounded Context, Blackjack Wallet, interacting with the DB). This is the external layer of the onion (still beneath the adapters-layer, though).
I've kept different namespaces in this layer for the different modules (Player, Table, Game) and used most method names for commands with bangs.

4.3 Core (Domain) layer

A purely functional layer. I've tried to simply port the java version's Domain here, minus the secondary (driving ports), e.g. the Game-, Player-, TableRepository or the WalletService, which I've decided to put under a port package on the same level as app and domain. Secondary ports neither defined (but in their own package) or used (but in app services, see previous point) here.

4.4. Infrastructure layer

Quite the same as in the java version. Adapters are implemented as Records of Protocols, see next point.

5. Using Protocols for secondary ports

I've tried to avoid OO-techniques as much as possible, but protocols simply seem to be the right way to implement ports.

6. Application Context and Dependency Injection

Although in the absence of objects FP doesn't require DI the same way as an OO program would, I've found the use of Protocols has similar needs to that of objects. The Protocol implementation has to be accessible to the function that uses it without explicit dependency on it. In a Java-Spring application the application context would do this wiring, as well as instantiating the proper implementation based on the content of a property file. Lacking any framework I created a dedicated namespace for it, called registry. All namespaces where functions need to call Protocols import the registry namespace. I don't know how others do this, but this solution seemed straightforward.

7. Modules

I kept the module structure of the Java version intact. Game, Table, Player and Cashier are the same, they interact with each other in an asynchronous way. Clojure Async does this job perfectly. There is a channel for Domain Events and subscriptions for the event handlers. Much shorter and cleaner than the Java version's was.

To be continued...

No comments :

Post a Comment