Functional architecture The pit of success Mark Seemann

























































![[<Fact>] let ``check returns right result at no prior reservations`` () = let capacity [<Fact>] let ``check returns right result at no prior reservations`` () = let capacity](https://slidetodoc.com/presentation_image/23ebba67b7b7569f16fe9793d91e3673/image-58.jpg)











- Slides: 69
Functional architecture The pit of success Mark Seemann http: //blog. ploeh. dk @ploeh
@ploeh
@ploeh
@ploeh
@ploeh
@ploeh
Parallelism Ports and adapters Services, Entities, Value Objects Testability @ploeh
PARALLISM
public void Update() { // when a generation has completed // now flip the back buffer so we can start // processing on the next generation var flip = this. next. Generation; this. next. Generation = this. world; this. world = flip; Generation++; this. Process. Generation(); } @ploeh
public void Update() { lock(this. sync. Lock) // when a generation has completed // now flip the back buffer so we can start // processing on the next generation var flip = this. next. Generation; this. next. Generation = this. world; this. world = flip; Generation++; this. Process. Generation(); } @ploeh
public void Update() { lock(this. sync. Lock) // when a generation has completed // now flip the back buffer so we can start // processing on the next generation var flip = this. next. Generation; this. next. Generation = this. world; this. world = flip; Generation++; this. Process. Generation(); } @ploeh
public void Update() { lock(this. sync. Lock) { // when a generation has completed // now flip the back buffer so we can start // processing on the next generation var flip = this. next. Generation; this. next. Generation = this. world; this. world = flip; Generation++; this. Process. Generation(); } @ploeh
public void Update() { lock(this. sync. Lock) { // when a generation has completed // now flip the back buffer so we can start // processing on the next generation var flip = this. next. Generation; this. next. Generation = this. world; this. world = flip; Generation++; this. Process. Generation(); } @ploeh
public void Update() { lock(this. sync. Lock) { // when a generation has completed // now flip the back buffer so we can start // processing on the next generation var flip = this. next. Generation; this. next. Generation = this. world; this. world = flip; Generation++; this. Process. Generation(); } @ploeh
public void Update() { lock(this. sync. Lock) { // when a generation has completed // now flip the back buffer so we can start // processing on the next generation var flip = this. next. Generation; this. next. Generation = this. world; this. world = flip; Generation++; this. Process. Generation(); } } @ploeh
let tick live. Cells = let is. Alive = function Alive -> true | _ -> false live. Cells |> Seq. collect find. Neighbors |> Seq. distinct |> Seq. filter (calculate. Next. State live. Cells >> is. Alive) |> Seq. to. Array |> Set. of. Array @ploeh
let tick live. Cells = let is. Alive = function Alive -> true | _ -> false live. Cells |> PSeq. collect find. Neighbors |> PSeq. distinct |> PSeq. filter (calculate. Next. State live. Cells >> is. Alive) |> PSeq. to. Array |> Set. of. Array @ploeh
PORTS AND ADAPTERS
@ploeh
Same return value for same input Pure function No sideeffects @ploeh
Impure function Pure function @ploeh
validate. Reservation : : Reservation. Rendition -> Either Error Reservation validate. Reservation r = case parse. Date (r. Date r) of Just d -> Right Reservation { date = d , name = r. Name r , email = r. Email r , quantity = r. Quantity r } Nothing -> Left (Validation. Error "Invalid date. ") @ploeh
get. Reserved. Seats. From. DB : : Connection. String -> Zoned. Time -> IO Int @ploeh
check. Capacity : : Int -> Reservation -> Either Error Reservation check. Capacity capacity reserved. Seats reservation = if capacity < quantity reservation + reserved. Seats then Left Capacity. Exceeded else Right reservation @ploeh
save. Reservation : : Connection. String -> Reservation -> IO () @ploeh
to. Http. Result : : Either Error () -> Http. Result () to. Http. Result (Left (Validation. Error msg)) = Bad. Request msg to. Http. Result (Left Capacity. Exceeded) = Status. Code Forbidden to. Http. Result (Right ()) = OK () @ploeh
post. Reservation : : Reservation. Rendition -> IO (Http. Result ()) post. Reservation candidate = fmap to. Http. Result $ run. Either. T $ do r <- hoist. Either $ validate. Reservation candidate i <- lift. IO $ get. Reserved. Seats. From. DB conn. Str $ date r hoist. Either $ check. Capacity 10 i r >>= lift. IO. save. Reservation conn. Str @ploeh
check. Capacity @ploeh
let imp candidate = either { let! r = Validate. reservation candidate let i = Sql. Gateway. get. Reserved. Seats connection. String r. Date let! r = Capacity. check 10 i r return Sql. Gateway. save. Reservation connection. String r } new Reservations. Controller(imp) : > _
SERVICES AND DATA
public class User { public int Id { get; } public string User. Name { get; } public static User Create. New(string user. Name) public static User Read(int id) public void Update() public void Delete() } @ploeh
public class User { public int Id { get; } public string User. Name { get; } public static User Create. New(string user. Name) public static User Read(int id) public void Update() public void Delete() } @ploeh
public class User { public int Id { get; } public string User. Name { get; } public static User Create. New(string user. Name) public static User Read(int id) public void Update() public void Delete() public void Send. Email(Email message) } @ploeh
@ploeh
Entities Services Value Objects @ploeh
public class User { public User(int id, string user. Name) public int Id { get } public string User. Name { get } } @ploeh
public class Sql. User. Repository : IUser. Repository { public User Create. New(string user. Name) public User Read(int id) public void Update(User user) public void Delete(User user) } @ploeh
public class Email. Sender : ISend. Email { public void Send. Email. To(User user, Email message) } @ploeh
Anaemic Model @ploeh
Anaemic Model @ploeh
Anaemic D Model @ploeh
Anaemic Do Model @ploeh
Anaemic Dom Model @ploeh
Anaemic Doma Model @ploeh
Anaemic Domai Model @ploeh
Anaemic Domain Model @ploeh
Entities Services Value Objects @ploeh
Data Functions @ploeh
type User = { Id : int; User. Name : string } @ploeh
module Persistence = // string -> User let create. New user. Name = //. . // int -> User let read id = //. . // User -> unit let update user = //. . // User -> unit let delete user = //. . @ploeh
module Mail = // User -> Email -> unit let send. Email. To user message = //. . @ploeh
public sealed class Money { public Money(decimal amount, string currency) public decimal Amount { get } public string Currency { get } public override bool Equals(object obj) public override int Get. Hash. Code() } @ploeh
type Money = { Amount : decimal; Currency : string } > { Amount = 1 m; Currency = "EUR" } = { Amount = 1 m; Currency = "EUR" }; ; val it : bool = true > { Amount = 2 m; Currency = "EUR" } = { Amount = 1 m; Currency = "EUR" }; ; val it : bool = false > { Amount = 1 m; Currency = "EUR" } = { Amount = 1 m; Currency = "GBP" }; ; val it : bool = false @ploeh
TESTABILITY
Test-induced damage https: //www. flickr. com/photos/davehamster/8667991007 @ploeh
public class Capacity. Checker : ICapacity. Checker { private readonly int capacity; private readonly IReservation. Repository repo; public Capacity. Checker(int capacity, IReservation. Repository repo) { this. capacity = capacity; this. repo = repo; } public bool Has. Capacity(Reservation reservation) { var reserved = this. repo. Get. Reserved. Seats(reservation. Date); return this. capacity < reservation. Quantity + reserved; } } @ploeh
let check capacity get. Reserved. Seats reservation = let reserved. Seats = get. Reserved. Seats reservation. Date if capacity < reservation. Quantity + reserved. Seats then Failure Capacity. Exceeded else Success reservation @ploeh
[<Fact>] let ``check returns right result at no prior reservations`` () = let capacity = 10 let get. Reserved. Seats _ = 0 let reservation = { Date = Date. Time. Offset(Date. Time(2014, 8, 10), Time. Span. From. Hours 2. ) Name = "Mark Seemann" Email = "mark@ploeh. dk" Quantity = 4 } let actual = Capacity. check capacity get. Reserved. Seats reservation let expected : Result<Reservation, Error> = Success reservation test <@ expected = actual @> @ploeh
Jessica Kerr Isolation When the only information a function has about the external world is passed into it via arguments. https: //twitter. com/jessitron @ploeh
Isolation Pure function @ploeh
Unit Test A unit test is an automated test that tests a unit in isolation from its dependencies. @ploeh
Unit Test A unit test is an automated test that tests a unit in isolation from its dependencies. @ploeh
Isolation Testinduced damage OOP @ploeh
Encapsulation Testability Object-Oriented Programming Isolation @ploeh
Functional Programming Testable Isolated Ideal function @ploeh
@ploeh
@ploeh
Parallelism Ports and adapters Services, Entities, Value Objects Testability @ploeh
Mark Seemann http: //blog. ploeh. dk http: //bit. ly/ploehralsight @ploeh