Initially this post was supposed to cover concepts of implementing an example DDD (Domain Driven Design) architecture using F# and Akka.NET. But the more I’ve written, the more I’ve realized that whole idea won’t fit into a single blog post. Therefore I’ve decided for now to focus on some basic example covering command/event handlers. To make this more interesting I want to show how Akka.NET and F# features can be used to leverage design established this way.
Before we begin…
I want to notice, that all examples in this post are made mostly to learning purposes. There are some things missing, but I’ve decided to leave them since they don’t have direct impact on the overall design or current state of the application. If I will continue this thread, I will probably try to extend them and provide some more meaningful context. So please don’t treat this code as a real-life reference of DDD implementation.
What is clearly missing in example below? I didn’t describe aggregate roots, because boundary context is very simplistic – to make this post clearer I’ve focused more on command-handler-event pattern. Sample doesn’t enforce any specific database, using simple dictionary as abstraction. Also it doesn’t provide any form of data validation or bindings to existing HTTP server frameworks. They are outside of scope of this post but are very simple to integrate.
Step 1: Define our domain data structures
Lets start with domain model. As a boundary context I’ve chosen one of the most generic ones – user account’s space. Our simplified business logic in this example will cover two actions – user registration and sign in. Users will be identified by their email and will use the most basic security features.
But before we move there, lets use some of the features, F# could give us, to shape a type system for our advantage.
type Email = string
type PasswordHash = byte array
These are examples of type aliases. You may consider using them, if you’ve found that usage of some primitive type in given context may be defined as subtype/supertype relation. In this example Email can be considered as a specialization of string – that means, each email is string, but not each string is a valid email. This kind of granularity can be stretched quite far. Just think about any primary/foreign key field in any entity as it’s own unique type. This will automatically disallow mistakes such as assigning keys from entities of incompatible type eg. assigning UserId
value to ProductId
field! F# expressiveness promotes this behavior.
Now use these types to define our User entity. It’s very straightforward:
type User =
{ email : Email
phash : PasswordHash
salt : byte array }
static member empty = { email = ""; phash = [||]; salt = [||] }
Next I’m going to describe data models for our domain commands and events. It’s important to understand purpose of both. Commands are produced to describe tasks to be executed by the system and contains all data necessary to perform it. Events are describing diffs in general application state as a results of performed tasks. What is wort mentioning, events should be described in way, that will allow us to recreate current application state by replaying all events timeline stream. And because events describe changes, that already occurred, they should be immutable by nature – don’t try to change your past if you not wish to mess with your future.
type UserCommand =
| RegisterUser of email : Email * password : string
| LoginUser of email : Email * password : string
NOTE: If you want to use F# records to describe your commands/events, that’s perfectly fine. I’ve chosen discriminated unions for their expressiveness.
type UserEvent =
| UserRegistered of email : Email * salt: byte array * phash : byte array * timestamp : DateTime
| UserLogged of email : Email * timestamp : DateTime
As you can see there are few things worth of emphasizing:
- We didn’t store user password provided directly from command, using salt + password hash instead. Reason behind is that in DDD events usually could be serialized and stored in some kind of event database. Therefore it’s not secure to provide them any sensitive data. However events should contain enough information to give us ability to recreate current database state based on provided stream of events, assuming that it’s complete.
- Each of our event’s contain a timestamp field. Events give us a historical change set of the application. And because they contain only diffs between database states rather than snapshot of entities, they have to be ordered by their creation date. Without it we couldn’t recreate them.
What other fields may be useful when working with events? Consider attaching current API version number – this will allow you to endure breaking changes in your code responsible for handling events. Other handy field is unique event id, when you may need functionality associated with generating cascading event chains in response to some other events. In that case having some kind of pointer to originating event may be useful.
Step 2: describe application behavior in functional manner
This may be a little controversial statement, but I must say it – exceptions are bad. They leaves the leaks in your type system and fucks with your application logic flow in the way, that many programmers and almost all compilers cannot understand.
The alternative conception of error handling is old and well-known. It’s based on returning error codes. To start with – if you already know Rust, this may look familiar to you:
type Result<'ok, 'err> =
| Ok of 'ok
| Error of 'err
You may ask: why the hell do we return error values instead of just throwing exceptions? Are these some archaeological excavations and we’re gone back to the C era? Actually no. There is very good reason behind this.
Think about exceptions as a side effects of the application state behavior. You cannot be 100% sure about flow of your application process. Since information about exceptions is not part of any function/method signature, they actually act as a dynamically typed cascading return values anyway – both compiler and programmers will ignore them until they will occur at runtime.
This time instead of cryptic error codes returned by C, we use F# power to provide type-safe and expressive way for this solution. If you’re interested more about interesting ways to utilize return-based error handling using higher-level concepts, I recommend you Railway Oriented Programming blogpost and video presented on NDC Oslo 2014. Here, I’ll use a greatly simplified version, exposing operator which chain two input functions together and call second one only if result from the first was Ok
, and forwarding an Error
result otherwise:
let (>=>) f1 f2 arg =
match f1 arg with
| Ok data -> f2 data
| Error e -> Error e
I’ve also decided to use more discriminated unions to describe each specific business logic error in actual bounded context scope. I’ve found that returning a string-based messages back to programmers in error logs are not better in any way than creating a specialized error type for each case. The only disadvantage is amount of code you need to write, to declare new type, but again – it’s not a problem with F#.
type UserError =
| UserAlreadyExists of userEmail : Email
| UserNotFound of userEmail : Email
| WrongPassword of userEmail : Email * hashedInput : PasswordHash
Step 3: handle user registration
How does F# command handling differs from it’s more objective oriented counterpart? Since we deal with functional oriented language, most of our logic is composed into functions instead of objects. You’ve used to execute your logic with services, here you’ve got a function. Wanna some Dependency Injection? Here, you have some currying.
I’ll describe our service as handle
function which takes some UserCommand
as an input and produces Result<UserEvent, UserError>
as output:
let handle clock findUser saveUser =
function
| RegisterUser(email, password) ->
match findUser email with
| Some _ -> Error(UserAlreadyExists email)
| None ->
let salt, phash = defaultGeneratePassword password
let user =
{ email = email
salt = salt
phash = phash }
saveUser user
Ok (UserRegistered(email, salt, phash, clock()))
As you’ve seen, this function actually takes 4 parameters (clock, findUser, saveUser and command as implicit argument of calling function keyword). But as I’ve said, our handle function should take only one input argument. That’s because we’ll provide three first arguments through partial function application later – now you can think about them as constructor arguments injected by your IoC container when resolving new service.
While most of the parameters are quite understandable, one of them may be confusing – the clock
parameter. It’s only functionality is to provide current DateTime value. If so, why didn’t I just used DateTime.Now
directly inside the code? In this example it’s actually not so important, but I’ve used it to expose simple problem – things such as date/configuration/environment providers or random number generators could make our handler behavior non-deterministic. That means, if we call this function two times with the same input, we couldn’t count on the same output. It’s actually problem for logic predictability and in some cases is especially cumbersome when writing tests to verify application behavior. Therefore I think that it’s better to separate them as injectable arguments.
Password hashing and generation
While you may find some good libraries providing real life hashing features out of hand, I’ve decided to leave my example free on unnecessary dependencies and use a standard library instead.
let private saltWith salt (p : string) =
let passBytes = System.Text.Encoding.UTF8.GetBytes p
Array.append passBytes salt
let sha512 (bytes : byte array) =
use sha = System.Security.Cryptography.SHA512.Create()
sha.ComputeHash bytes
let hashPassword hashFn salt password = hashFn (saltWith salt password)
let generatePassword hashFn saltSize password =
use saltGen = System.Security.Cryptography.RandomNumberGenerator.Create()
let salt = Array.zeroCreate saltSize
saltGen.GetBytes salt
(salt, hashPassword hashFn salt password)
let inline defaultGeneratePassword pass = generatePassword sha512 64 pass
let inline defaultHashPassword salt pass = hashPassword sha512 salt pass
I hope this code doesn’t need any explanations.
Step 4: handle user logging
Once we already described nuances of the function-based services, this one shouldn’t be a problem. This case was created to give our example more diversity.
let handle clock findUser saveUser =
function
| RegisterUser(email, password) -> ...
| LoginUser(email, password, remember) ->
match findUser email with
| None -> Error(UserNotFound email)
| Some user ->
let computedPasswordHash = defaultHashPassword (user.salt) password
if computedPasswordHash = user.phash then
Ok (UserLogged(user.email, clock()))
else Error(WrongPassword(user.email, computedPasswordHash))
Mock database access
As I’ve said in the introduction, I won’t use any real-life database provider, mocking it with concurrent dictionary instead. However if you want to use a normal database, nothing stands in your way.
let userStore = ConcurrentDictionary()
let inline flip f x y = f y x
let findInUserStore email (store : ConcurrentDictionary<string, User>) =
match store.TryGetValue email with
| (false, _) -> None
| (true, user) -> Some user
let saveInUserStore user (store : ConcurrentDictionary<string, User>) =
store.AddOrUpdate(user.email, user, System.Func<_, _, _>(fun key old -> user)) |> ignore
F# is heavily function-oriented and this also corresponds to arguments precedence. While in OOP subject (this) usually precedes method invocation, in functional languages it’s better to use it as last argument of the function. Argumentation for this is currying and partial function application, which allows us to define functions with only part of all necessary arguments provided. The more specific argument, the later it could be applied. Therefore common convention is to provide more detailed parameters at the end of the function parameters list. It’s a very useful feature, especially when combined with pipeline operators.
On the other side flip
function may be used to reverse parameters precedence in case when, for example, we want to partially apply second argument to the function without providing first one (because it may be yet unknown in this moment). This option will be presented later.
Handle event subscribers
One of the nice things in Akka is that it provides a Publish/Subscribe pattern offhand. As an actor-based framework, the only necessary task to do is to subscribe an ActorRef
of desired observer directly to Akka system’s event stream.
As a lazy person I don’t want to be forced to explicitly create new actor each time I want to subscribe some behavior to react for events I’ve produced somewhere else. Therefore I’ve created simple subscribe helper, which will handle subscriber actor creation for me.
// automatic unique concurrent name generator
let mutable subscriptionNr = 0
let inline getSubNr() = System.Threading.Interlocked.Increment(&subscriptionNr)
let subscribe system (fn : 'a -> unit) =
let subId = getSubNr()
let subscriber = spawn system ("subscriber-" + (string subId)) <| actAs fn
system.EventStream.Subscribe(subscriber, typeof<'a>) |> ignore
subscriber
let publish (bus : Akka.Event.EventStream) evt =
bus.Publish evt
Ok evt
Bind everything together
Now when all logic has been defined, it’s time to show it in some example code. It will initialize a new Akka system, setup console logging and show features of successful and unsuccessful registering and signing in of the user.
let inline defaultClock() = DateTime.Now
// display any errors returned by command handler
let handleErrors = function
| Ok _ -> ()
| Error data -> printf "User error: %A\n" data
let system = System.create "system" <| Akka.Configuration.ConfigurationFactory.Default()
let userHandler =
// inject "dependencies" into handle function
handle
<| defaultClock
<| flip findInUserStore userStore
<| flip saveInUserStore userStore
let subscriber = subscribe system <| printf "User event: %A\n"
let userActor = spawn system "users" <| actorOf (userHandler >=> (publish system.EventStream) >> handleErrors)
userActor <! RegisterUser("j.doe@testmail.co", "pass")
userActor <! RegisterUser("j.doe@testmail.co", "pass") // UserAlreadyExists error
System.Threading.Thread.Sleep 100
userActor <! LoginUser("j.doe@testmail.co", "pass")
userActor <! LoginUser("j.doe@testmail.co", "fails") // WrongPassword error
System.Threading.Thread.Sleep 100
system.Shutdown()
That’s all. Whole example took about 130 lines of F# code to create. I hope this would give you some insights about embedding your business logic into F# function composition model.
PS: If you’re interested in more details about DDD and F# programming, at the present moment I can recommend you following other exploring lecture of the other developers, such as Lev Gorodinski (@eulerfx) and his blog for more advanced concepts.
Comments