Functional DDD

31
Functional Domain Driven Design disclaimer: is still a “work in progress”

description

How functional languages, as F#, fit in domain driven design's world

Transcript of Functional DDD

  • 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(null, new CartCreated { CartId=1 }) Commands, events andfold (step 5)
  • 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)
  • Q & A