How functional languages, as F#, fit in domain driven design's world
Citation preview
Functional Domain Driven Design disclaimer: is still a work in
progress
Focus on the domain and domain logic rather than technology
(Eric Evans) Perche non provare un approccio funzionale?
A monad is a triple (T, , ) where T is an endofunctor T:
X->X and : I->T and : T x T- >T are 2 natural
transformations satisfying these laws: Identity law: ((T)) = T =
(T()) Associative law: ((T T) T)) = (T (T T)) A monad in X is just
a monoid in the category of endofunctors of X, with product
replaced by composition of endofunctors and unit set by the
identity endofunctor What is a monad?
software developer @ codiceplastico @amelchiori
[email protected] About me
public class Contact { public String FirstName { get; set; }
public String MiddleName { get; set; } public String LastName {
get; set; } public String EMail { get; set; } public Boolean
IsEMailVerified { get; set; } }
public class PersonalName { public String FirstName { get;
private set; } public String MiddleName { get; private set; }
public String LastName { get; private set; } public
PersonalName(String firstName, String secondName, String
middleName=null) { // check if firstName or secondName are null (or
invalid) FirstName = firstName; SecondName = secondName; MiddleName
= middleName; } } Value object
public class PersonalName { // same as before public override
Boolean Equals(Object other) { var target = other as PersonalName;
return target == null ? false : target.FirstName == this.FirstName
&& target.LastName == this.LastName &&
target.MiddleName == this.MiddleName; } public override Int32
GetHashCode() { // ... } } Value objects equality
type PersonalName = { FirstName : string; LastName : string;
MiddleName : string } Value object in F# option; let name = {
FirstName = Alessandro; LastName = Melchiori; }
type Person = { Id: int; Name: PersonalName; } [] with override
this.GetHasCode() = hash this.Id override this.Equals(other) =
match other with | :? Person as x -> (this.Id = x.Id) | _ ->
false Entity in F#
module CardGame = type Move = Rock | Scissor | Paper type
Player = Player of string type Game = Player * Move type Result =
Player | Draw type Round = (Game * Game) -> Result The design is
the code, and the code is the design Ubiquitous language in F#
Email is just a string? No. Create a type :) type Email=Email
of string let createEmail (value: string) = if value.Length Email
option Type obsession
public class Contact { public String FirstName { get; set; }
public String MiddleName { get; set; } public String LastName {
get; set; } public String EMail { get; set; } public Boolean
IsEMailVerified { get; set; } }
Rule 1: When email change, verification must be reset to false
Rule 2: Email verification should be made only by a specific
service Business logic
type EmailContact = { Email: Email, IsVerified: bool } Business
logic in F#
type VerifiedEmail = VerifiedEmail of Email type
VerificationService = ( Email * EmailHash ) -> VerifiedEmail
option type EmailContact = | Unverified of Email | Verified of
VerifiedEmail Business logic in F#
type Name = Name of string type PersonalName = { FirstName:
Name, MiddleName: Name option, LastName: Name } type Email = Email
of string type VerifiedEmail = VerifiedEmail of Email type
EmailContact = | Unverified of Email | Verified of VerifiedEmail
type Contact = { Name: PersonalName, Email: EmailContact }
Ubiquitous language to the rescue
Make illegal states unrepresentable Ubiquitous language to the
rescue
public void AddItemToCart(Item item) { // validation if (item
== null) throw new ArgumentNullException(); // execution
_items.Add(item.Id); } Commands, events andfold (step 1)
public void AddItemToCart(Item item) { if (item == null) throw
new ArgumentNullException(); var domainEvent = new ItemAddedToCart
{ CartId = this.Id, ItemId = item.Id }; Apply(domainEvent) }
private void Apply(ItemAddedToCart domainEvent) {
_items.Add(domainEvent.ItemId); } Commands, events andfold (step
2)
public void AddItemToCart(Item item) { if (item == null) throw
new ArgumentNullException(); var domainEvent = new ItemAddedToCart
{ CartId = this.Id, ItemId = item.Id }; Apply(this, domainEvent) }
private void Apply(Cart target, ItemAddedToCart domainEvent) {
target._items.Add(domainEvent.ItemId); } Commands, events andfold
(step 3)
public static Cart Apply(Cart target, CartCreated domainEvent)
{ return new Cart { Id = domainEvent.CartId, _items = new String[0]
}; } public static Cart Apply(Cart target, ItemAddedToCart
domainEvent) { var items = target._items.ToList();
items.Add(domainEvent.ItemId); return new Cart { Id =
domainEvent.CartId, _items = items }; } public static Cart
Apply(Cart target, ItemRemovedFromCart domainEvent) { var items =
target._items.ToList(); items.Remove(domainEvent.ItemId); return
new Cart { Id = domainEvent.CartId, _items = items }; } Commands,
events andfold (step 4)
Cart.Apply( Cart.Apply(null, new CartCreated { CartId=1}), new
ItemAddedToCart { CartId = 1, ItemId = "A" } ) Commands, events
andfold (step 5)
Cart.Apply( Cart.Apply( Cart.Apply(null, new CartCreated {
CartId=1}), new ItemAddedToCart { CartId = 1, ItemId = "A" } ), new
ItemAddedToCart { CartId = 1, ItemId = "B" } ) Commands, events
andfold (step 5)
Cart.Apply( Cart.Apply( Cart.Apply( Cart.Apply(null, new
CartCreated { CartId=1}), new ItemAddedToCart { CartId = 1, ItemId
= "A" } ), new ItemAddedToCart { CartId = 1, ItemId = "B" } ), new
ItemRemovedFromCart { CartId = 1, ItemId = "A" } ) Commands, events
andfold (step 5)
Executing a command: type Exec = ( CartState * Command ) ->
DomainEvent Applying an event: type Apply = ( CartState *
DomainEvent ) -> CartState Commands, events andfold (step
6)
type Command = | Create of string | AddItem of int | RemoveItem
of int | RemoveAllItems | Checkout type Event = | Created of string
| ItemAdded of int | ItemRemoved of int | AllItemsRemoved |
Checkouted Commands, events andfold (step 7)
type CartState = { Name: string; Items: List; Active: bool; }
let apply state = function | Created x -> { Cart.empty with Name
= x } | ItemAdded x -> { state with Items = List.append
state.Items [x] } | ItemRemoved x -> { state with Items =
List.filter (fun i -> i x ) state.Items } | Removed _ -> {
state with Items = List.empty } | Checkouted _ -> { state with
Active = false } Commands, events andfold (step 8)
let domainEvents = [ Created("cart1"); ItemAdded(1);
ItemAdded(2); Removed; ItemAdded(3); Checkouted; ] let state =
List.fold apply Cart.empty domainEvents Commands, events andfold
(step 9)