├── .gitignore ├── .paket └── paket.bootstrapper.exe ├── LICENSE ├── README.md ├── build.cmd ├── build.fsx ├── build.sh ├── ex1 ├── README.md ├── done │ ├── LibAAS.App │ │ ├── App.config │ │ ├── App.fs │ │ ├── App.fsx │ │ ├── AssemblyInfo.fs │ │ ├── LibAAS.App.fsproj │ │ └── Program.fs │ ├── LibAAS.Contracts │ │ ├── AssemblyInfo.fs │ │ ├── Commands.fs │ │ ├── Events.fs │ │ ├── LibAAS.Contracts.fsproj │ │ ├── Script.fsx │ │ └── Types.fs │ ├── LibAAS.Domain │ │ ├── App.config │ │ ├── AssemblyInfo.fs │ │ ├── CommandHandling.fs │ │ ├── DomainEntry.fs │ │ ├── DomainTypes.fs │ │ ├── Inventory.fs │ │ ├── LibAAS.Domain.fsproj │ │ └── Loan.fs │ ├── LibAAS.Infrastructure │ │ ├── AgentHelper.fs │ │ ├── AssemblyInfo.fs │ │ ├── ErrorHandling.fs │ │ ├── EventStore.fs │ │ ├── LibAAS.Infrastructure.fsproj │ │ └── Script.fsx │ ├── LibAAS.Tests │ │ ├── AssemblyInfo.fs │ │ ├── InventoryTests.fs │ │ ├── LibAAS.Tests.fsproj │ │ ├── LoanTests.fs │ │ ├── Specification.fs │ │ ├── TestHelpers.fs │ │ ├── app.config │ │ └── packages.config │ └── LibAAS.sln └── start │ ├── LibAAS.App │ ├── App.config │ ├── App.fs │ ├── App.fsx │ ├── AssemblyInfo.fs │ ├── LibAAS.App.fsproj │ └── Program.fs │ ├── LibAAS.Contracts │ ├── AssemblyInfo.fs │ ├── Commands.fs │ ├── Events.fs │ ├── LibAAS.Contracts.fsproj │ ├── Script.fsx │ └── Types.fs │ ├── LibAAS.Domain │ ├── App.config │ ├── AssemblyInfo.fs │ ├── CommandHandling.fs │ ├── DomainEntry.fs │ ├── DomainTypes.fs │ ├── Inventory.fs │ ├── LibAAS.Domain.fsproj │ └── Loan.fs │ ├── LibAAS.Infrastructure │ ├── AgentHelper.fs │ ├── AssemblyInfo.fs │ ├── ErrorHandling.fs │ ├── EventStore.fs │ ├── LibAAS.Infrastructure.fsproj │ └── Script.fsx │ ├── LibAAS.Tests │ ├── AssemblyInfo.fs │ ├── InventoryTests.fs │ ├── LibAAS.Tests.fsproj │ ├── LoanTests.fs │ ├── Specification.fs │ ├── TestHelpers.fs │ ├── app.config │ └── packages.config │ └── LibAAS.sln ├── ex2 ├── README.md ├── done │ ├── LibAAS.App │ │ ├── App.config │ │ ├── App.fs │ │ ├── App.fsx │ │ ├── AssemblyInfo.fs │ │ ├── LibAAS.App.fsproj │ │ └── Program.fs │ ├── LibAAS.Contracts │ │ ├── AssemblyInfo.fs │ │ ├── Commands.fs │ │ ├── Events.fs │ │ ├── LibAAS.Contracts.fsproj │ │ ├── Script.fsx │ │ └── Types.fs │ ├── LibAAS.Domain │ │ ├── App.config │ │ ├── AssemblyInfo.fs │ │ ├── CommandHandling.fs │ │ ├── DomainEntry.fs │ │ ├── DomainTypes.fs │ │ ├── Inventory.fs │ │ ├── LibAAS.Domain.fsproj │ │ └── Loan.fs │ ├── LibAAS.Infrastructure │ │ ├── AgentHelper.fs │ │ ├── AssemblyInfo.fs │ │ ├── ErrorHandling.fs │ │ ├── EventStore.fs │ │ ├── LibAAS.Infrastructure.fsproj │ │ └── Script.fsx │ ├── LibAAS.Tests │ │ ├── AssemblyInfo.fs │ │ ├── InventoryTests.fs │ │ ├── LibAAS.Tests.fsproj │ │ ├── LoanTests.fs │ │ ├── Specification.fs │ │ ├── TestHelpers.fs │ │ ├── app.config │ │ └── packages.config │ └── LibAAS.sln └── start │ ├── LibAAS.App │ ├── App.config │ ├── App.fs │ ├── App.fsx │ ├── AssemblyInfo.fs │ ├── LibAAS.App.fsproj │ └── Program.fs │ ├── LibAAS.Contracts │ ├── AssemblyInfo.fs │ ├── Commands.fs │ ├── Events.fs │ ├── LibAAS.Contracts.fsproj │ ├── Script.fsx │ └── Types.fs │ ├── LibAAS.Domain │ ├── App.config │ ├── AssemblyInfo.fs │ ├── CommandHandling.fs │ ├── DomainEntry.fs │ ├── DomainTypes.fs │ ├── Inventory.fs │ ├── LibAAS.Domain.fsproj │ └── Loan.fs │ ├── LibAAS.Infrastructure │ ├── AgentHelper.fs │ ├── AssemblyInfo.fs │ ├── ErrorHandling.fs │ ├── EventStore.fs │ ├── LibAAS.Infrastructure.fsproj │ └── Script.fsx │ ├── LibAAS.Tests │ ├── AssemblyInfo.fs │ ├── InventoryTests.fs │ ├── LibAAS.Tests.fsproj │ ├── LoanTests.fs │ ├── Specification.fs │ ├── TestHelpers.fs │ ├── app.config │ └── packages.config │ └── LibAAS.sln ├── ex3 ├── README.md ├── done │ ├── LibAAS.App │ │ ├── App.config │ │ ├── App.fs │ │ ├── App.fsx │ │ ├── AssemblyInfo.fs │ │ ├── LibAAS.App.fsproj │ │ └── Program.fs │ ├── LibAAS.Contracts │ │ ├── AssemblyInfo.fs │ │ ├── Commands.fs │ │ ├── Events.fs │ │ ├── LibAAS.Contracts.fsproj │ │ ├── Script.fsx │ │ └── Types.fs │ ├── LibAAS.Domain │ │ ├── App.config │ │ ├── AssemblyInfo.fs │ │ ├── CommandHandling.fs │ │ ├── DomainEntry.fs │ │ ├── DomainTypes.fs │ │ ├── Inventory.fs │ │ ├── LibAAS.Domain.fsproj │ │ └── Loan.fs │ ├── LibAAS.Infrastructure │ │ ├── AgentHelper.fs │ │ ├── AssemblyInfo.fs │ │ ├── ErrorHandling.fs │ │ ├── EventStore.fs │ │ ├── LibAAS.Infrastructure.fsproj │ │ └── Script.fsx │ ├── LibAAS.Tests │ │ ├── AssemblyInfo.fs │ │ ├── InventoryTests.fs │ │ ├── LibAAS.Tests.fsproj │ │ ├── LoanTests.fs │ │ ├── Specification.fs │ │ ├── TestHelpers.fs │ │ ├── app.config │ │ └── packages.config │ └── LibAAS.sln └── start │ ├── LibAAS.App │ ├── App.config │ ├── App.fs │ ├── App.fsx │ ├── AssemblyInfo.fs │ ├── LibAAS.App.fsproj │ └── Program.fs │ ├── LibAAS.Contracts │ ├── AssemblyInfo.fs │ ├── Commands.fs │ ├── Events.fs │ ├── LibAAS.Contracts.fsproj │ ├── Script.fsx │ └── Types.fs │ ├── LibAAS.Domain │ ├── App.config │ ├── AssemblyInfo.fs │ ├── CommandHandling.fs │ ├── DomainEntry.fs │ ├── DomainTypes.fs │ ├── Inventory.fs │ ├── LibAAS.Domain.fsproj │ └── Loan.fs │ ├── LibAAS.Infrastructure │ ├── AgentHelper.fs │ ├── AssemblyInfo.fs │ ├── ErrorHandling.fs │ ├── EventStore.fs │ ├── LibAAS.Infrastructure.fsproj │ └── Script.fsx │ ├── LibAAS.Tests │ ├── AssemblyInfo.fs │ ├── InventoryTests.fs │ ├── LibAAS.Tests.fsproj │ ├── LoanTests.fs │ ├── Specification.fs │ ├── TestHelpers.fs │ ├── app.config │ └── packages.config │ └── LibAAS.sln ├── ex4 ├── README.md ├── done │ ├── LibAAS.App │ │ ├── App.config │ │ ├── App.fs │ │ ├── App.fsx │ │ ├── AssemblyInfo.fs │ │ ├── LibAAS.App.fsproj │ │ └── Program.fs │ ├── LibAAS.Contracts │ │ ├── AssemblyInfo.fs │ │ ├── Commands.fs │ │ ├── Events.fs │ │ ├── LibAAS.Contracts.fsproj │ │ ├── Script.fsx │ │ └── Types.fs │ ├── LibAAS.Domain │ │ ├── App.config │ │ ├── AssemblyInfo.fs │ │ ├── CommandHandling.fs │ │ ├── DomainEntry.fs │ │ ├── DomainTypes.fs │ │ ├── Inventory.fs │ │ ├── LibAAS.Domain.fsproj │ │ └── Loan.fs │ ├── LibAAS.Infrastructure │ │ ├── AgentHelper.fs │ │ ├── AssemblyInfo.fs │ │ ├── ErrorHandling.fs │ │ ├── EventStore.fs │ │ ├── LibAAS.Infrastructure.fsproj │ │ └── Script.fsx │ ├── LibAAS.Tests │ │ ├── AssemblyInfo.fs │ │ ├── InventoryTests.fs │ │ ├── LibAAS.Tests.fsproj │ │ ├── LoanTests.fs │ │ ├── Specification.fs │ │ ├── TestHelpers.fs │ │ ├── app.config │ │ └── packages.config │ └── LibAAS.sln └── start │ ├── LibAAS.App │ ├── App.config │ ├── App.fs │ ├── App.fsx │ ├── AssemblyInfo.fs │ ├── LibAAS.App.fsproj │ └── Program.fs │ ├── LibAAS.Contracts │ ├── AssemblyInfo.fs │ ├── Commands.fs │ ├── Events.fs │ ├── LibAAS.Contracts.fsproj │ ├── Script.fsx │ └── Types.fs │ ├── LibAAS.Domain │ ├── App.config │ ├── AssemblyInfo.fs │ ├── CommandHandling.fs │ ├── DomainEntry.fs │ ├── DomainTypes.fs │ ├── Inventory.fs │ ├── LibAAS.Domain.fsproj │ └── Loan.fs │ ├── LibAAS.Infrastructure │ ├── AgentHelper.fs │ ├── AssemblyInfo.fs │ ├── ErrorHandling.fs │ ├── EventStore.fs │ ├── LibAAS.Infrastructure.fsproj │ └── Script.fsx │ ├── LibAAS.Tests │ ├── AssemblyInfo.fs │ ├── InventoryTests.fs │ ├── LibAAS.Tests.fsproj │ ├── LoanTests.fs │ ├── Specification.fs │ ├── TestHelpers.fs │ ├── app.config │ └── packages.config │ └── LibAAS.sln ├── paket.dependencies └── paket.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | node_modules/ 5 | dist/ 6 | 7 | # mstest test results 8 | TestResults 9 | 10 | ## Ignore Visual Studio temporary files, build results, and 11 | ## files generated by popular Visual Studio add-ons. 12 | 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.sln.docstates 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Rr]elease/ 21 | x64/ 22 | *_i.c 23 | *_p.c 24 | *.ilk 25 | *.meta 26 | *.obj 27 | *.pch 28 | *.pdb 29 | *.pgc 30 | *.pgd 31 | *.rsp 32 | *.sbr 33 | *.tlb 34 | *.tli 35 | *.tlh 36 | *.tmp 37 | *.log 38 | *.vspscc 39 | *.vssscc 40 | .builds 41 | .build 42 | .test 43 | .deploy 44 | 45 | # Visual C++ cache files 46 | ipch/ 47 | *.aps 48 | *.ncb 49 | *.opensdf 50 | *.sdf 51 | 52 | # Visual Studio profiler 53 | *.psess 54 | *.vsp 55 | *.vspx 56 | 57 | # Guidance Automation Toolkit 58 | *.gpState 59 | 60 | # ReSharper is a .NET coding add-in 61 | _ReSharper* 62 | 63 | # NCrunch 64 | *.ncrunch* 65 | .*crunch*.local.xml 66 | 67 | # Installshield output folder 68 | [Ee]xpress 69 | 70 | # DocProject is a documentation generator add-in 71 | DocProject/buildhelp/ 72 | DocProject/Help/*.HxT 73 | DocProject/Help/*.HxC 74 | DocProject/Help/*.hhc 75 | DocProject/Help/*.hhk 76 | DocProject/Help/*.hhp 77 | DocProject/Help/Html2 78 | DocProject/Help/html 79 | 80 | # Click-Once directory 81 | publish 82 | 83 | # Publish Web Output 84 | *.Publish.xml 85 | 86 | # NuGet Packages Directory 87 | packages 88 | 89 | # Windows Azure Build Output 90 | csx 91 | *.build.csdef 92 | 93 | # Windows Store app package directory 94 | AppPackages/ 95 | 96 | # Others 97 | [Bb]in 98 | [Oo]bj 99 | sql 100 | TestResults 101 | [Tt]est[Rr]esult* 102 | *.Cache 103 | ClientBin 104 | [Ss]tyle[Cc]op.* 105 | ~$* 106 | *.dbmdl 107 | Generated_Code #added for RIA/Silverlight projects 108 | 109 | # Backup & report files from converting an old project file to a newer 110 | # Visual Studio version. Backup files are not needed, because we have git ;-) 111 | _UpgradeReport_Files/ 112 | Backup*/ 113 | UpgradeLog*.XML 114 | 115 | src/Rapporteringsregisteret.Web/assets/less/*.css 116 | 117 | MetricResults/ 118 | *.sln.ide/ 119 | 120 | _configs/ 121 | .fake/ 122 | .paket/ -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mastoj/LibAAS/f70da85093375b2d1bfd078cb26896795cc39eef/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tomas Jansson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | ".paket/paket.bootstrapper.exe" 2 | ".paket/paket.exe" "restore" 3 | 4 | "./packages/FAKE/tools/FAKE.exe" %* "--fsiargs" "build.fsx" 5 | -------------------------------------------------------------------------------- /build.fsx: -------------------------------------------------------------------------------- 1 | #r @"packages/FAKE/tools/FakeLib.dll" 2 | open System 3 | open Fake 4 | open Fake.Testing.XUnit2 5 | 6 | let testDir = ".test" 7 | 8 | let targetName = getBuildParam "target" 9 | trace (sprintf "Target name: %s" targetName) 10 | let targets = ["Ex1Start";"Ex1Done";"Ex2Start";"Ex2Done";"Ex3Start";"Ex3Done";"Ex4Start";"Ex4Start"] |> List.map (fun s -> s.ToLower()) 11 | 12 | if targets |> List.contains (targetName.ToLower()) |> not then 13 | let targetNames = String.Join("|", targets) 14 | let msg = sprintf "Missing target, use: ./build.sh <%s>" targetNames 15 | targets |> String.concat "|" |> sprintf "Missing target, use: ./build.sh <%s>" |> traceError 16 | exit -1 17 | let (proj,version) = (targetName.Substring(0,3), targetName.Substring(3)) 18 | let basePath = sprintf "./%s/%s" proj version 19 | 20 | // Add targets so they are found by the Ionide FAKE plugin 21 | Target "Ex1Start" ignore 22 | Target "Ex1Done" ignore 23 | Target "Ex2Start" ignore 24 | Target "Ex2Done" ignore 25 | Target "Ex3Start" ignore 26 | Target "Ex3Done" ignore 27 | Target "Ex4Start" ignore 28 | Target "Ex4Done" ignore 29 | 30 | Target "Default" (fun _ -> 31 | trace "Hello default" 32 | ) 33 | 34 | Target "RestorePackages" (fun _ -> 35 | let packagesFolder = basePath "packages" 36 | !!(basePath "**/packages.config") 37 | |> Seq.iter 38 | (RestorePackage (fun parameters -> 39 | { parameters with 40 | OutputPath = packagesFolder})) 41 | ) 42 | 43 | Target "Build" (fun _ -> 44 | trace (sprintf "Building %s %s" proj version) 45 | let sln = !! (basePath "*.sln") 46 | trace (sprintf "Will build solution: %A" sln) 47 | sln 48 | |> MSBuildDebug "" "Rebuild" 49 | |> ignore 50 | ) 51 | 52 | Target "Test" (fun _ -> 53 | let testDlls = !!(basePath "*.Tests/bin/Debug/*.Tests.dll") 54 | testDlls 55 | |> xUnit2 (fun p -> p) 56 | ) 57 | 58 | "RestorePackages" 59 | ==> "Build" 60 | ==> "Test" 61 | ==> "Default" 62 | 63 | Run "Default" 64 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function run() { 4 | mono "$@" 5 | } 6 | 7 | run .paket/paket.bootstrapper.exe 8 | run .paket/paket.exe restore 9 | 10 | run ./packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx 11 | #run packages/FAKE/tools/FAKE.exe "$@" $FSIARGS build.fsx -------------------------------------------------------------------------------- /ex1/done/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | //let itemId = ItemId 4 14 | //let item = ( itemId, 15 | // Book { 16 | // Title = Title "A book" 17 | // Author = Author "A author"}) 18 | // 19 | //let qty = Quantity.Create 10 20 | //let aggId = AggregateId 4 21 | //let command = aggId, RegisterInventoryItem (item, qty) 22 | //let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/done/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | 7 | [] 8 | let main argv = 9 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 10 | 11 | let logSubscriber e = 12 | printfn "Hey ho! Lets go!" 13 | printfn "%A" e 14 | printfn "We went!" 15 | 16 | eventStore.AddSubscriber "logsub" logSubscriber 17 | 18 | let random = new Random() 19 | let newRandomInt() = random.Next() 20 | let newAggId() = AggregateId (newRandomInt()) 21 | 22 | 23 | let loanGuid = newRandomInt() 24 | // let userId = UserId (newGuid()) 25 | // let itemId = ItemId (newRandomInt()) 26 | // let libraryId = LibraryId (newGuid()) 27 | // let aggId = AggregateId loanGuid 28 | // let loan = { LoanId = LoanId loanGuid 29 | // UserId = userId 30 | // ItemId = itemId 31 | // LibraryId = libraryId } 32 | // 33 | // let item = ( loan.ItemId, 34 | // Book 35 | // { Title = Title "A book" 36 | // Author = Author "A author"}) 37 | // 38 | // let executer = execute eventStore 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/done/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem of LoanItem 6 | | ReturnItem of ReturnItem 7 | | RegisterInventoryItem of RegisterInventoryItem 8 | 9 | and LoanItem = unit 10 | 11 | and ReturnItem = unit 12 | 13 | and RegisterInventoryItem = { 14 | Item:Item 15 | Quantity:Quantity } 16 | 17 | type Command = AggregateId * CommandData 18 | 19 | 20 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = 6 | | ItemRegistered of item:Item * Quantity:Quantity 7 | 8 | type Events = AggregateId * EventData list 9 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | 7 | type ItemId = ItemId of int 8 | type Title = Title of string 9 | type Author = Author of string 10 | type Book = {Title: Title; Author: Author } 11 | type Quantity = private Quantity of int 12 | with 13 | static member Create x = 14 | if x >= 0 then Quantity x 15 | else raise (exn "Invalid quantity") 16 | type ItemData = 17 | | Book of Book 18 | type Item = ItemId*ItemData 19 | 20 | type Version = int 21 | type Error = 22 | | NotImplemented of string 23 | | VersionConflict of string 24 | | InvalidStateTransition of string 25 | | InvalidState of string 26 | | InvalidItem 27 | 28 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/done/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | 8 | type LoanState = 9 | | LoanInit 10 | 11 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 12 | type EvolveSeed<'T> = 13 | { 14 | Init: 'T 15 | EvolveOne: EvolveOne<'T> 16 | } 17 | 18 | type InternalDependencies = 19 | { 20 | GetItem: ItemId -> Result 21 | } 22 | 23 | type StateGetters = 24 | { 25 | GetInventoryItem: ItemId -> Result 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | [ItemRegistered(command.Item, command.Quantity)] |> ok 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | ItemInit, (id, RegisterInventoryItem cmd) -> handleAtInit (id, cmd) 13 | | _ -> InvalidState "Inventory" |> fail 14 | 15 | let evolveAtInit = function 16 | | _ -> raise (exn "Implement me") 17 | 18 | let evolveOne (event:EventData) state = 19 | match state with 20 | | _ -> raise (exn "Implement me") 21 | 22 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex1/done/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | raise (exn "Implement me") 8 | 9 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 10 | raise (exn "Implement me") 11 | 12 | let executeCommand state stateGetters command = 13 | match state, command with 14 | | _ -> raise (exn "Implement me") 15 | 16 | let evolveAtInit = function 17 | | _ -> raise (exn "Implement me") 18 | 19 | let evolveAtCreated data = function 20 | | _ -> raise (exn "Implement me") 21 | 22 | let evolveOne (event:EventData) state = 23 | match state with 24 | | _ -> raise (exn "Implement me") 25 | 26 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 27 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/done/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex1/done/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open LibAAS.Infrastructure 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/done/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``the item should be added``() = 12 | let itemIntId = newRandomInt() 13 | let aggId = AggregateId itemIntId 14 | let itemId = ItemId itemIntId 15 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 16 | let item = itemId,book 17 | let qty = Quantity.Create 10 18 | 19 | Given defaultPreconditions 20 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 21 | |> Then ([ItemRegistered(item, qty)] |> ok) 22 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Tests/LoanTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.LoanTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Domain 5 | open LibAAS.Tests.Specification 6 | open LibAAS.Tests.TestHelpers 7 | open Xunit 8 | 9 | module ``When loaning an item`` = 10 | 11 | let implementME = () 12 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex1/done/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | //let itemId = ItemId 4 14 | //let item = ( itemId, 15 | // Book { 16 | // Title = Title "A book" 17 | // Author = Author "A author"}) 18 | // 19 | //let qty = Quantity.Create 10 20 | //let aggId = AggregateId 4 21 | //let command = aggId, RegisterInventoryItem (item, qty) 22 | //let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/start/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | 7 | [] 8 | let main argv = 9 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 10 | 11 | let logSubscriber e = 12 | printfn "Hey ho! Lets go!" 13 | printfn "%A" e 14 | printfn "We went!" 15 | 16 | eventStore.AddSubscriber "logsub" logSubscriber 17 | 18 | let random = new Random() 19 | let newRandomInt() = random.Next() 20 | let newAggId() = AggregateId (newRandomInt()) 21 | 22 | 23 | let loanGuid = newRandomInt() 24 | // let userId = UserId (newGuid()) 25 | // let itemId = ItemId (newRandomInt()) 26 | // let libraryId = LibraryId (newGuid()) 27 | // let aggId = AggregateId loanGuid 28 | // let loan = { LoanId = LoanId loanGuid 29 | // UserId = userId 30 | // ItemId = itemId 31 | // LibraryId = libraryId } 32 | // 33 | // let item = ( loan.ItemId, 34 | // Book 35 | // { Title = Title "A book" 36 | // Author = Author "A author"}) 37 | // 38 | // let executer = execute eventStore 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/start/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem 6 | | ReturnItem 7 | | RegisterInventoryItem 8 | 9 | and LoanItem = unit 10 | and ReturnItem = unit 11 | and RegisterInventoryItem = unit 12 | 13 | type Command = AggregateId * CommandData 14 | 15 | 16 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = int 6 | 7 | type Events = AggregateId * EventData list 8 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | type ItemId = ItemId of int 7 | 8 | type Version = int 9 | 10 | type Error = 11 | | NotImplemented of string 12 | | VersionConflict of string 13 | | InvalidStateTransition of string 14 | | InvalidState of string 15 | | InvalidItem 16 | 17 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/start/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | 8 | type LoanState = 9 | | LoanInit 10 | 11 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 12 | type EvolveSeed<'T> = 13 | { 14 | Init: 'T 15 | EvolveOne: EvolveOne<'T> 16 | } 17 | 18 | type InternalDependencies = 19 | { 20 | GetItem: ItemId -> Result 21 | } 22 | 23 | type StateGetters = 24 | { 25 | GetInventoryItem: ItemId -> Result 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | raise (exn "Implement me") 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | _ -> raise (exn "Implement me") 13 | 14 | let evolveAtInit = function 15 | | _ -> raise (exn "Implement me") 16 | 17 | let evolveOne (event:EventData) state = 18 | match state with 19 | | _ -> raise (exn "Implement me") 20 | 21 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex1/start/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | raise (exn "Implement me") 8 | 9 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 10 | raise (exn "Implement me") 11 | 12 | let executeCommand state stateGetters command = 13 | match state, command with 14 | | _ -> raise (exn "Implement me") 15 | 16 | let evolveAtInit = function 17 | | _ -> raise (exn "Implement me") 18 | 19 | let evolveAtCreated data = function 20 | | _ -> raise (exn "Implement me") 21 | 22 | let evolveOne (event:EventData) state = 23 | match state with 24 | | _ -> raise (exn "Implement me") 25 | 26 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 27 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/start/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex1/start/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open LibAAS.Infrastructure 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex1/start/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``The item should be added``() = () 12 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Tests/LoanTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.LoanTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Domain 5 | open LibAAS.Tests.Specification 6 | open LibAAS.Tests.TestHelpers 7 | open Xunit 8 | 9 | [] 10 | module LoanTestsHelpers = () 11 | 12 | module ``When loaning an item`` = 13 | 14 | let implementME = () 15 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex1/start/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | //let itemId = ItemId 4 14 | //let item = ( itemId, 15 | // Book { 16 | // Title = Title "A book" 17 | // Author = Author "A author"}) 18 | // 19 | //let qty = Quantity.Create 10 20 | //let aggId = AggregateId 4 21 | //let command = aggId, RegisterInventoryItem (item, qty) 22 | //let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/done/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | 7 | [] 8 | let main argv = 9 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 10 | 11 | let logSubscriber e = 12 | printfn "Hey ho! Lets go!" 13 | printfn "%A" e 14 | printfn "We went!" 15 | 16 | eventStore.AddSubscriber "logsub" logSubscriber 17 | 18 | let random = new Random() 19 | let newRandomInt() = random.Next() 20 | let newAggId() = AggregateId (newRandomInt()) 21 | 22 | 23 | let loanGuid = newRandomInt() 24 | // let userId = UserId (newGuid()) 25 | // let itemId = ItemId (newRandomInt()) 26 | // let libraryId = LibraryId (newGuid()) 27 | // let aggId = AggregateId loanGuid 28 | // let loan = { LoanId = LoanId loanGuid 29 | // UserId = userId 30 | // ItemId = itemId 31 | // LibraryId = libraryId } 32 | // 33 | // let item = ( loan.ItemId, 34 | // Book 35 | // { Title = Title "A book" 36 | // Author = Author "A author"}) 37 | // 38 | // let executer = execute eventStore 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/done/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem of LoanItem 6 | | ReturnItem of ReturnItem 7 | | RegisterInventoryItem of RegisterInventoryItem 8 | 9 | and LoanItem = unit 10 | 11 | and ReturnItem = unit 12 | 13 | and RegisterInventoryItem = { 14 | Item:Item 15 | Quantity:Quantity } 16 | 17 | 18 | type Command = AggregateId * CommandData 19 | 20 | 21 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = 6 | | ItemRegistered of item:Item * Quantity:Quantity 7 | 8 | type Events = AggregateId * EventData list 9 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | 7 | type ItemId = ItemId of int 8 | type Title = Title of string 9 | type Author = Author of string 10 | type Book = {Title: Title; Author: Author } 11 | type Quantity = private Quantity of int 12 | with 13 | static member Create x = 14 | if x >= 0 then Quantity x 15 | else raise (exn "Invalid quantity") 16 | type ItemData = 17 | | Book of Book 18 | type Item = ItemId*ItemData 19 | 20 | type Version = int 21 | type Error = 22 | | NotImplemented of string 23 | | VersionConflict of string 24 | | InvalidStateTransition of string 25 | | InvalidState of string 26 | | InvalidItem 27 | 28 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/done/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | | ItemInStock of item:Item*quantity:Quantity 8 | 9 | type LoanState = 10 | | LoanInit 11 | 12 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 13 | type EvolveSeed<'T> = 14 | { 15 | Init: 'T 16 | EvolveOne: EvolveOne<'T> 17 | } 18 | 19 | type InternalDependencies = 20 | { 21 | GetItem: ItemId -> Result 22 | } 23 | 24 | type StateGetters = 25 | { 26 | GetInventoryItem: ItemId -> Result 27 | } 28 | 29 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | [ItemRegistered(command.Item, command.Quantity)] |> ok 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | ItemInit, (id, RegisterInventoryItem cmd) -> handleAtInit (id, cmd) 13 | | _ -> InvalidState "Inventory" |> fail 14 | 15 | let evolveAtInit = function 16 | | ItemRegistered(item, quantity) -> ItemInStock (item, quantity) |> ok 17 | 18 | let evolveOne (event:EventData) state = 19 | match state with 20 | | ItemInit -> evolveAtInit event 21 | 22 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex2/done/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | raise (exn "Implement me") 8 | 9 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 10 | raise (exn "Implement me") 11 | 12 | let executeCommand state stateGetters command = 13 | match state, command with 14 | | _ -> raise (exn "Implement me") 15 | 16 | let evolveAtInit = function 17 | | _ -> raise (exn "Implement me") 18 | 19 | let evolveAtCreated data = function 20 | | _ -> raise (exn "Implement me") 21 | 22 | let evolveOne (event:EventData) state = 23 | match state with 24 | | _ -> raise (exn "Implement me") 25 | 26 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 27 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/done/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex2/done/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open LibAAS.Infrastructure 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/done/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``the item should be added``() = 12 | let itemIntId = newRandomInt() 13 | let aggId = AggregateId itemIntId 14 | let itemId = ItemId itemIntId 15 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 16 | let item = itemId,book 17 | let qty = Quantity.Create 10 18 | 19 | Given defaultPreconditions 20 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 21 | |> Then ([ItemRegistered(item, qty)] |> ok) 22 | 23 | [] 24 | let ``The item should not be added if the id is not unique``() = 25 | let itemIntId = newRandomInt() 26 | let aggId = AggregateId itemIntId 27 | let itemId = ItemId itemIntId 28 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 29 | let item = itemId,book 30 | let qty = Quantity.Create 10 31 | 32 | Given { 33 | defaultPreconditions 34 | with 35 | presets = [aggId, [ItemRegistered(item, qty)]] } 36 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 37 | |> Then (InvalidState "Inventory" |> fail) -------------------------------------------------------------------------------- /ex2/done/LibAAS.Tests/LoanTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.LoanTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Domain 5 | open LibAAS.Tests.Specification 6 | open LibAAS.Tests.TestHelpers 7 | open Xunit 8 | 9 | module ``When loaning an item`` = 10 | 11 | let implementME = () 12 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex2/done/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | //let itemId = ItemId 4 14 | //let item = ( itemId, 15 | // Book { 16 | // Title = Title "A book" 17 | // Author = Author "A author"}) 18 | // 19 | //let qty = Quantity.Create 10 20 | //let aggId = AggregateId 4 21 | //let command = aggId, RegisterInventoryItem (item, qty) 22 | //let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/start/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | 7 | [] 8 | let main argv = 9 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 10 | 11 | let logSubscriber e = 12 | printfn "Hey ho! Lets go!" 13 | printfn "%A" e 14 | printfn "We went!" 15 | 16 | eventStore.AddSubscriber "logsub" logSubscriber 17 | 18 | let random = new Random() 19 | let newRandomInt() = random.Next() 20 | let newAggId() = AggregateId (newRandomInt()) 21 | 22 | 23 | let loanGuid = newRandomInt() 24 | // let userId = UserId (newGuid()) 25 | // let itemId = ItemId (newRandomInt()) 26 | // let libraryId = LibraryId (newGuid()) 27 | // let aggId = AggregateId loanGuid 28 | // let loan = { LoanId = LoanId loanGuid 29 | // UserId = userId 30 | // ItemId = itemId 31 | // LibraryId = libraryId } 32 | // 33 | // let item = ( loan.ItemId, 34 | // Book 35 | // { Title = Title "A book" 36 | // Author = Author "A author"}) 37 | // 38 | // let executer = execute eventStore 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/start/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem of LoanItem 6 | | ReturnItem of ReturnItem 7 | | RegisterInventoryItem of RegisterInventoryItem 8 | 9 | and LoanItem = unit 10 | 11 | and ReturnItem = unit 12 | 13 | and RegisterInventoryItem = { 14 | Item:Item 15 | Quantity:Quantity } 16 | 17 | type Command = AggregateId * CommandData 18 | 19 | 20 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = 6 | | ItemRegistered of item:Item * Quantity:Quantity 7 | 8 | type Events = AggregateId * EventData list 9 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | 7 | type ItemId = ItemId of int 8 | type Title = Title of string 9 | type Author = Author of string 10 | type Book = {Title: Title; Author: Author } 11 | type Quantity = private Quantity of int 12 | with 13 | static member Create x = 14 | if x >= 0 then Quantity x 15 | else raise (exn "Invalid quantity") 16 | type ItemData = 17 | | Book of Book 18 | type Item = ItemId*ItemData 19 | 20 | type Version = int 21 | type Error = 22 | | NotImplemented of string 23 | | VersionConflict of string 24 | | InvalidStateTransition of string 25 | | InvalidState of string 26 | | InvalidItem 27 | 28 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/start/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | 8 | type LoanState = 9 | | LoanInit 10 | 11 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 12 | type EvolveSeed<'T> = 13 | { 14 | Init: 'T 15 | EvolveOne: EvolveOne<'T> 16 | } 17 | 18 | type InternalDependencies = 19 | { 20 | GetItem: ItemId -> Result 21 | } 22 | 23 | type StateGetters = 24 | { 25 | GetInventoryItem: ItemId -> Result 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | [ItemRegistered(command.Item, command.Quantity)] |> ok 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | ItemInit, (id, RegisterInventoryItem cmd) -> handleAtInit (id, cmd) 13 | | _ -> InvalidState "Inventory" |> fail 14 | 15 | let evolveAtInit = function 16 | | _ -> raise (exn "Implement me") 17 | 18 | let evolveOne (event:EventData) state = 19 | match state with 20 | | _ -> raise (exn "Implement me") 21 | 22 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex2/start/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | raise (exn "Implement me") 8 | 9 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 10 | raise (exn "Implement me") 11 | 12 | let executeCommand state stateGetters command = 13 | match state, command with 14 | | _ -> raise (exn "Implement me") 15 | 16 | let evolveAtInit = function 17 | | _ -> raise (exn "Implement me") 18 | 19 | let evolveAtCreated data = function 20 | | _ -> raise (exn "Implement me") 21 | 22 | let evolveOne (event:EventData) state = 23 | match state with 24 | | _ -> raise (exn "Implement me") 25 | 26 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 27 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/start/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex2/start/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open LibAAS.Infrastructure 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex2/start/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``the item should be added``() = 12 | let itemIntId = newRandomInt() 13 | let aggId = AggregateId itemIntId 14 | let itemId = ItemId itemIntId 15 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 16 | let item = itemId,book 17 | let qty = Quantity.Create 10 18 | 19 | Given defaultPreconditions 20 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 21 | |> Then ([ItemRegistered(item, qty)] |> ok) 22 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Tests/LoanTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.LoanTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Domain 5 | open LibAAS.Tests.Specification 6 | open LibAAS.Tests.TestHelpers 7 | open Xunit 8 | 9 | module ``When loaning an item`` = 10 | 11 | let implementME = () 12 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex2/start/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | //let itemId = ItemId 4 14 | //let item = ( itemId, 15 | // Book { 16 | // Title = Title "A book" 17 | // Author = Author "A author"}) 18 | // 19 | //let qty = Quantity.Create 10 20 | //let aggId = AggregateId 4 21 | //let command = aggId, RegisterInventoryItem (item, qty) 22 | //let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/done/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | 7 | [] 8 | let main argv = 9 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 10 | 11 | let logSubscriber e = 12 | printfn "Hey ho! Lets go!" 13 | printfn "%A" e 14 | printfn "We went!" 15 | 16 | eventStore.AddSubscriber "logsub" logSubscriber 17 | 18 | let random = new Random() 19 | let newRandomInt() = random.Next() 20 | let newAggId() = AggregateId (newRandomInt()) 21 | 22 | 23 | let loanGuid = newRandomInt() 24 | // let userId = UserId (newGuid()) 25 | // let itemId = ItemId (newRandomInt()) 26 | // let libraryId = LibraryId (newGuid()) 27 | // let aggId = AggregateId loanGuid 28 | // let loan = { LoanId = LoanId loanGuid 29 | // UserId = userId 30 | // ItemId = itemId 31 | // LibraryId = libraryId } 32 | // 33 | // let item = ( loan.ItemId, 34 | // Book 35 | // { Title = Title "A book" 36 | // Author = Author "A author"}) 37 | // 38 | // let executer = execute eventStore 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/done/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem of LoanItem 6 | | ReturnItem of ReturnItem 7 | | RegisterInventoryItem of RegisterInventoryItem 8 | 9 | and LoanItem = { 10 | Id:LoanId 11 | UserId:UserId 12 | ItemId:ItemId 13 | LibraryId:LibraryId } 14 | 15 | and ReturnItem = { 16 | Id:LoanId } 17 | 18 | and RegisterInventoryItem = { 19 | Item:Item 20 | Quantity:Quantity } 21 | 22 | type Command = AggregateId * CommandData 23 | 24 | 25 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = 6 | | ItemLoaned of loan:Loan*loanDate:LoanDate*dueDate:DueDate 7 | | ItemRegistered of item:Item * Quantity:Quantity 8 | 9 | type Events = AggregateId * EventData list 10 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | 7 | type ItemId = ItemId of int 8 | type Title = Title of string 9 | type Author = Author of string 10 | type UserId = UserId of int 11 | type LibraryId = LibraryId of int 12 | type LoanId = LoanId of int 13 | type Book = {Title: Title; Author: Author } 14 | type Quantity = private Quantity of int 15 | with 16 | static member Create x = 17 | if x >= 0 then Quantity x 18 | else raise (exn "Invalid quantity") 19 | type ItemData = 20 | | Book of Book 21 | type Item = ItemId*ItemData 22 | 23 | type LoanDate = LoanDate of DateTime 24 | type DueDate = DueDate of DateTime 25 | 26 | type Loan = { LoanId: LoanId; UserId: UserId; ItemId: ItemId; LibraryId: LibraryId } 27 | 28 | type Version = int 29 | type Error = 30 | | NotImplemented of string 31 | | VersionConflict of string 32 | | InvalidStateTransition of string 33 | | InvalidState of string 34 | | InvalidItem 35 | 36 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/done/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | | ItemInStock of item:Item*quantity:Quantity 8 | 9 | type LoanState = 10 | | LoanInit 11 | 12 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 13 | type EvolveSeed<'T> = 14 | { 15 | Init: 'T 16 | EvolveOne: EvolveOne<'T> 17 | } 18 | 19 | type InternalDependencies = 20 | { 21 | GetItem: ItemId -> Result 22 | } 23 | 24 | type StateGetters = 25 | { 26 | GetInventoryItem: ItemId -> Result 27 | } 28 | 29 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | [ItemRegistered(command.Item, command.Quantity)] |> ok 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | ItemInit, (id, RegisterInventoryItem cmd) -> handleAtInit (id, cmd) 13 | | _ -> InvalidState "Inventory" |> fail 14 | 15 | let evolveAtInit = function 16 | | ItemRegistered(item, quantity) -> ItemInStock (item, quantity) |> ok 17 | 18 | let evolveOne (event:EventData) state = 19 | match state with 20 | | ItemInit -> evolveAtInit event 21 | 22 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex3/done/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | commandData.ItemId |> 8 | (stateGetters.GetInventoryItem 9 | >=> function 10 | | ItemInit -> InvalidItem |> fail 11 | | _ -> 12 | let loan = 13 | { LoanId = commandData.Id 14 | UserId = commandData.UserId 15 | ItemId = commandData.ItemId 16 | LibraryId = commandData.LibraryId } 17 | let now = DateTime.Today 18 | [ItemLoaned (loan, LoanDate now, DueDate (now.AddDays(7.)))] |> ok) 19 | 20 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 21 | raise (exn "Implement me") 22 | 23 | let executeCommand state stateGetters command = 24 | match state, command with 25 | | LoanInit, (id, LoanItem data) -> handleAtInit stateGetters (id, data) 26 | | _ -> InvalidState "Loan" |> fail 27 | 28 | let evolveAtInit = function 29 | | _ -> raise (exn "Implement me") 30 | 31 | let evolveAtCreated data = function 32 | | _ -> raise (exn "Implement me") 33 | 34 | let evolveOne (event:EventData) state = 35 | match state with 36 | | _ -> raise (exn "Implement me") 37 | 38 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 39 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/done/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex3/done/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open LibAAS.Infrastructure 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/done/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``the item should be added``() = 12 | let itemIntId = newRandomInt() 13 | let aggId = AggregateId itemIntId 14 | let itemId = ItemId itemIntId 15 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 16 | let item = itemId,book 17 | let qty = Quantity.Create 10 18 | 19 | Given defaultPreconditions 20 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 21 | |> Then ([ItemRegistered(item, qty)] |> ok) 22 | 23 | [] 24 | let ``The item should not be added if the id is not unique``() = 25 | let itemIntId = newRandomInt() 26 | let aggId = AggregateId itemIntId 27 | let itemId = ItemId itemIntId 28 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 29 | let item = itemId,book 30 | let qty = Quantity.Create 10 31 | 32 | Given { 33 | defaultPreconditions 34 | with 35 | presets = [aggId, [ItemRegistered(item, qty)]] } 36 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 37 | |> Then (InvalidState "Inventory" |> fail) -------------------------------------------------------------------------------- /ex3/done/LibAAS.Tests/LoanTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.LoanTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Domain 5 | open LibAAS.Tests.Specification 6 | open LibAAS.Tests.TestHelpers 7 | open Xunit 8 | 9 | [] 10 | module LoanTestsHelpers = 11 | let createLoanTestData() = 12 | let loanIntId = newRandomInt() 13 | let userId = UserId (newRandomInt()) 14 | let itemId = ItemId (newRandomInt()) 15 | let libraryId = LibraryId (newRandomInt()) 16 | let aggId = AggregateId loanIntId 17 | aggId, { LoanId = LoanId loanIntId 18 | UserId = userId 19 | ItemId = itemId 20 | LibraryId = libraryId } 21 | 22 | let today() = System.DateTime.Today 23 | 24 | module ``When loaning an item`` = 25 | 26 | [] 27 | let ``the loan should be created and due date set``() = 28 | let aggId,loan = createLoanTestData() 29 | 30 | let item = ( loan.ItemId, 31 | Book 32 | { Title = Title "A book" 33 | Author = Author "A author"}) 34 | 35 | let qty = Quantity.Create 10 36 | let (ItemId id) = loan.ItemId 37 | let itemAggId = AggregateId id 38 | 39 | Given {defaultPreconditions 40 | with 41 | presets = [itemAggId, [ItemRegistered(item, qty)]]} 42 | |> When (aggId, LoanItem { Id = loan.LoanId; UserId = loan.UserId; ItemId = loan.ItemId; LibraryId = loan.LibraryId }) 43 | |> Then ([ItemLoaned 44 | ( loan, 45 | LoanDate System.DateTime.Today, 46 | DueDate (System.DateTime.Today.AddDays(7.)))] |> ok) 47 | 48 | [] 49 | let ``the user shoul be notified if item doesn't exist``() = 50 | let aggId,loan = createLoanTestData() 51 | 52 | Given defaultPreconditions 53 | |> When (aggId, LoanItem { Id = loan.LoanId; UserId = loan.UserId; ItemId = loan.ItemId; LibraryId = loan.LibraryId }) 54 | |> Then (InvalidItem |> fail) 55 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex3/done/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | 10 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | let itemId = ItemId 4 14 | let item = ( itemId, 15 | Book { 16 | Title = Title "A book" 17 | Author = Author "A author"}) 18 | 19 | let qty = Quantity.Create 10 20 | let aggId = AggregateId 4 21 | let command = aggId, RegisterInventoryItem (item, qty) 22 | let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/start/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | 7 | [] 8 | let main argv = 9 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 10 | 11 | let logSubscriber e = 12 | printfn "Hey ho! Lets go!" 13 | printfn "%A" e 14 | printfn "We went!" 15 | 16 | eventStore.AddSubscriber "logsub" logSubscriber 17 | 18 | let random = new Random() 19 | let newRandomInt() = random.Next() 20 | let newAggId() = AggregateId (newRandomInt()) 21 | 22 | 23 | let loanGuid = newRandomInt() 24 | // let userId = UserId (newGuid()) 25 | // let itemId = ItemId (newRandomInt()) 26 | // let libraryId = LibraryId (newGuid()) 27 | // let aggId = AggregateId loanGuid 28 | // let loan = { LoanId = LoanId loanGuid 29 | // UserId = userId 30 | // ItemId = itemId 31 | // LibraryId = libraryId } 32 | // 33 | // let item = ( loan.ItemId, 34 | // Book 35 | // { Title = Title "A book" 36 | // Author = Author "A author"}) 37 | // 38 | // let executer = execute eventStore 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/start/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem of LoanItem 6 | | ReturnItem of ReturnItem 7 | | RegisterInventoryItem of RegisterInventoryItem 8 | 9 | and LoanItem = unit 10 | 11 | and ReturnItem = unit 12 | 13 | and RegisterInventoryItem = { 14 | Item:Item 15 | Quantity:Quantity } 16 | 17 | type Command = AggregateId * CommandData -------------------------------------------------------------------------------- /ex3/start/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = 6 | | ItemRegistered of item:Item * Quantity:Quantity 7 | 8 | type Events = AggregateId * EventData list 9 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | 7 | type ItemId = ItemId of int 8 | type Title = Title of string 9 | type Author = Author of string 10 | type Book = {Title: Title; Author: Author } 11 | type Quantity = private Quantity of int 12 | with 13 | static member Create x = 14 | if x >= 0 then Quantity x 15 | else raise (exn "Invalid quantity") 16 | type ItemData = 17 | | Book of Book 18 | type Item = ItemId*ItemData 19 | 20 | type Version = int 21 | type Error = 22 | | NotImplemented of string 23 | | VersionConflict of string 24 | | InvalidStateTransition of string 25 | | InvalidState of string 26 | | InvalidItem 27 | 28 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/start/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | | ItemInStock of item:Item*quantity:Quantity 8 | 9 | type LoanState = 10 | | LoanInit 11 | 12 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 13 | type EvolveSeed<'T> = 14 | { 15 | Init: 'T 16 | EvolveOne: EvolveOne<'T> 17 | } 18 | 19 | type InternalDependencies = 20 | { 21 | GetItem: ItemId -> Result 22 | } 23 | 24 | type StateGetters = 25 | { 26 | GetInventoryItem: ItemId -> Result 27 | } 28 | 29 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | [ItemRegistered(command.Item, command.Quantity)] |> ok 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | ItemInit, (id, RegisterInventoryItem cmd) -> handleAtInit (id, cmd) 13 | | _ -> InvalidState "Inventory" |> fail 14 | 15 | let evolveAtInit = function 16 | | ItemRegistered(item, quantity) -> ItemInStock (item, quantity) |> ok 17 | 18 | let evolveOne (event:EventData) state = 19 | match state with 20 | | ItemInit -> evolveAtInit event 21 | 22 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex3/start/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | raise (exn "Implement me") 8 | 9 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 10 | raise (exn "Implement me") 11 | 12 | let executeCommand state stateGetters command = 13 | match state, command with 14 | | _ -> raise (exn "Implement me") 15 | 16 | let evolveAtInit = function 17 | | _ -> raise (exn "Implement me") 18 | 19 | let evolveAtCreated data = function 20 | | _ -> raise (exn "Implement me") 21 | 22 | let evolveOne (event:EventData) state = 23 | match state with 24 | | _ -> raise (exn "Implement me") 25 | 26 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 27 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/start/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex3/start/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open LibAAS.Infrastructure 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex3/start/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``the item should be added``() = 12 | let itemIntId = newRandomInt() 13 | let aggId = AggregateId itemIntId 14 | let itemId = ItemId itemIntId 15 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 16 | let item = itemId,book 17 | let qty = Quantity.Create 10 18 | 19 | Given defaultPreconditions 20 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 21 | |> Then ([ItemRegistered(item, qty)] |> ok) 22 | 23 | [] 24 | let ``The item should not be added if the id is not unique``() = 25 | let itemIntId = newRandomInt() 26 | let aggId = AggregateId itemIntId 27 | let itemId = ItemId itemIntId 28 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 29 | let item = itemId,book 30 | let qty = Quantity.Create 10 31 | 32 | Given { 33 | defaultPreconditions 34 | with 35 | presets = [aggId, [ItemRegistered(item, qty)]] } 36 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 37 | |> Then (InvalidState "Inventory" |> fail) -------------------------------------------------------------------------------- /ex3/start/LibAAS.Tests/LoanTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.LoanTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Domain 5 | open LibAAS.Tests.Specification 6 | open LibAAS.Tests.TestHelpers 7 | open Xunit 8 | 9 | module ``When loaning an item`` = 10 | 11 | let implementME = () 12 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex3/start/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | let itemId = ItemId 4 14 | let item = ( itemId, 15 | Book { 16 | Title = Title "A book" 17 | Author = Author "A author"}) 18 | 19 | let qty = Quantity.Create 10 20 | let aggId = AggregateId 4 21 | let command = aggId, RegisterInventoryItem (item, qty) 22 | let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/done/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | open LibAAS.AppBuilder 7 | 8 | [] 9 | let main argv = 10 | let (eventStore,app) = createApp() 11 | 12 | let logSubscriber e = 13 | printfn "Hey ho! Lets go!" 14 | printfn "%A" e 15 | printfn "We went!" 16 | 17 | eventStore.AddSubscriber "logsub" logSubscriber 18 | 19 | let random = new Random() 20 | let newRandomInt() = random.Next() 21 | let newAggId() = AggregateId (newRandomInt()) 22 | 23 | 24 | let loanGuid = newRandomInt() 25 | // let userId = UserId (newGuid()) 26 | // let itemId = ItemId (newRandomInt()) 27 | // let libraryId = LibraryId (newGuid()) 28 | // let aggId = AggregateId loanGuid 29 | // let loan = { LoanId = LoanId loanGuid 30 | // UserId = userId 31 | // ItemId = itemId 32 | // LibraryId = libraryId } 33 | // 34 | // let item = ( loan.ItemId, 35 | // Book 36 | // { Title = Title "A book" 37 | // Author = Author "A author"}) 38 | // 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/done/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem of LoanItem 6 | | ReturnItem of ReturnItem 7 | | RegisterInventoryItem of RegisterInventoryItem 8 | 9 | and LoanItem = { 10 | Id:LoanId 11 | UserId:UserId 12 | ItemId:ItemId 13 | LibraryId:LibraryId } 14 | 15 | and ReturnItem = { 16 | Id:LoanId } 17 | 18 | and RegisterInventoryItem = { 19 | Item:Item 20 | Quantity:Quantity } 21 | 22 | type Command = AggregateId * CommandData 23 | 24 | 25 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = 6 | | ItemLoaned of loan:Loan*loanDate:LoanDate*dueDate:DueDate 7 | | ItemRegistered of item:Item * Quantity:Quantity 8 | | ItemReturned of loan:Loan*returnDate:ReturnDate 9 | | ItemLate of loan:Loan*returnDate:ReturnDate*numberOfDaysLate:int*fine:Fine 10 | 11 | type Events = AggregateId * EventData list 12 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | 7 | type ItemId = ItemId of int 8 | type Title = Title of string 9 | type Author = Author of string 10 | type UserId = UserId of int 11 | type LibraryId = LibraryId of int 12 | type LoanId = LoanId of int 13 | type Book = {Title: Title; Author: Author } 14 | type Quantity = private Quantity of int 15 | with 16 | static member Create x = 17 | if x >= 0 then Quantity x 18 | else raise (exn "Invalid quantity") 19 | type ItemData = 20 | | Book of Book 21 | type Item = ItemId*ItemData 22 | 23 | type LoanDate = LoanDate of DateTime 24 | type DueDate = DueDate of DateTime 25 | 26 | type ReturnDate = ReturnDate of DateTime 27 | type Fine = Fine of int 28 | 29 | type Loan = { LoanId: LoanId; UserId: UserId; ItemId: ItemId; LibraryId: LibraryId } 30 | 31 | type Version = int 32 | type Error = 33 | | NotImplemented of string 34 | | VersionConflict of string 35 | | InvalidStateTransition of string 36 | | InvalidState of string 37 | | InvalidItem 38 | 39 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/done/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | | ItemInStock of item:Item*quantity:Quantity 8 | 9 | type LoanData = {Loan: Loan; LoanDate: LoanDate; DueDate: DueDate} 10 | type LoanState = 11 | | LoanInit 12 | | LoanCreated of LoanData 13 | 14 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 15 | type EvolveSeed<'T> = 16 | { 17 | Init: 'T 18 | EvolveOne: EvolveOne<'T> 19 | } 20 | 21 | type InternalDependencies = 22 | { 23 | GetItem: ItemId -> Result 24 | } 25 | 26 | type StateGetters = 27 | { 28 | GetInventoryItem: ItemId -> Result 29 | } 30 | 31 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | [ItemRegistered(command.Item, command.Quantity)] |> ok 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | ItemInit, (id, RegisterInventoryItem cmd) -> handleAtInit (id, cmd) 13 | | _ -> InvalidState "Inventory" |> fail 14 | 15 | let evolveAtInit = function 16 | | ItemRegistered(item, quantity) -> ItemInStock (item, quantity) |> ok 17 | 18 | let evolveOne (event:EventData) state = 19 | match state with 20 | | ItemInit -> evolveAtInit event 21 | 22 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex4/done/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | commandData.ItemId |> 8 | (stateGetters.GetInventoryItem 9 | >=> function 10 | | ItemInit -> InvalidItem |> fail 11 | | _ -> 12 | let loan = 13 | { LoanId = commandData.Id 14 | UserId = commandData.UserId 15 | ItemId = commandData.ItemId 16 | LibraryId = commandData.LibraryId } 17 | let now = DateTime.Today 18 | [ItemLoaned (loan, LoanDate now, DueDate (now.AddDays(7.)))] |> ok) 19 | 20 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 21 | let now = DateTime.Today 22 | let (DueDate duedate) = data.DueDate 23 | let daysLate = (now - duedate).Days 24 | let fine = 100 * daysLate 25 | if now > duedate then 26 | [ItemLate (data.Loan, ReturnDate now, daysLate, Fine fine )] |> ok 27 | else 28 | [ItemReturned (data.Loan, ReturnDate now )] |> ok 29 | 30 | let executeCommand state stateGetters command = 31 | match state, command with 32 | | LoanInit, (id, LoanItem data) -> handleAtInit stateGetters (id, data) 33 | | LoanCreated data, (id, ReturnItem cmd) -> (id, cmd) |> handleAtCreated data 34 | | _ -> InvalidState "Loan" |> fail 35 | 36 | let evolveAtInit = function 37 | | ItemLoaned (loan, loanDate, dueDate) -> 38 | LoanCreated {Loan = loan; DueDate = dueDate; LoanDate = loanDate} |> ok 39 | | _ -> InvalidStateTransition "Loan at init" |> fail 40 | 41 | let evolveAtCreated data = function 42 | | _ -> raise (exn "Implement me") 43 | 44 | let evolveOne (event:EventData) state = 45 | match state with 46 | | LoanInit -> evolveAtInit event 47 | | _ -> raise (exn "Implement me") 48 | 49 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 50 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/done/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex4/done/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | #load "AgentHelper.fs" 2 | #load "ErrorHandling.fs" 3 | #load "EventStore.fs" 4 | 5 | open EventStore 6 | 7 | let inMemoryEventStore = createInMemoryEventStore "This is a version error" 8 | inMemoryEventStore.AddSubscriber "FirstSubscriber" (printfn "%A") 9 | let res0 = inMemoryEventStore.SaveEvents (StreamId 1) (StreamVersion 0) ["Hello";"World"] 10 | let res1 = inMemoryEventStore.SaveEvents (StreamId 1) (StreamVersion 1) ["Hello2";"World2"] 11 | let res2 = inMemoryEventStore.SaveEvents (StreamId 1) (StreamVersion 2) ["Hello2";"World2"] 12 | 13 | [res0;res1;res2] |> List.mapi (fun i v -> printfn "%i: %A" i v) -------------------------------------------------------------------------------- /ex4/done/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/done/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``the item should be added``() = 12 | let itemIntId = newRandomInt() 13 | let aggId = AggregateId itemIntId 14 | let itemId = ItemId itemIntId 15 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 16 | let item = itemId,book 17 | let qty = Quantity.Create 10 18 | 19 | Given defaultPreconditions 20 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 21 | |> Then ([ItemRegistered(item, qty)] |> ok) 22 | 23 | [] 24 | let ``The item should not be added if the id is not unique``() = 25 | let itemIntId = newRandomInt() 26 | let aggId = AggregateId itemIntId 27 | let itemId = ItemId itemIntId 28 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 29 | let item = itemId,book 30 | let qty = Quantity.Create 10 31 | 32 | Given { 33 | defaultPreconditions 34 | with 35 | presets = [aggId, [ItemRegistered(item, qty)]] } 36 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 37 | |> Then (InvalidState "Inventory" |> fail) -------------------------------------------------------------------------------- /ex4/done/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ex4/done/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.App/App.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.AppBuilder 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | 6 | let createApp() = 7 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 8 | (eventStore, execute eventStore) 9 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.App/App.fsx: -------------------------------------------------------------------------------- 1 | #I "bin/Debug" 2 | 3 | #r "LibAAS.Contracts" 4 | #r "LibAAS.Infrastructure" 5 | #r "LibAAS.Domain" 6 | 7 | #load "App.fs" 8 | open LibAAS.AppBuilder 9 | open LibAAS.Contracts 10 | 11 | let (eventStore,app) = createApp() 12 | 13 | //let itemId = ItemId 4 14 | //let item = ( itemId, 15 | // Book { 16 | // Title = Title "A book" 17 | // Author = Author "A author"}) 18 | // 19 | //let qty = Quantity.Create 10 20 | //let aggId = AggregateId 4 21 | //let command = aggId, RegisterInventoryItem (item, qty) 22 | //let result = command |> app 23 | printfn "Result %A" result 24 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.App/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/start/LibAAS.App/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open System 6 | 7 | [] 8 | let main argv = 9 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Version conflict") 10 | 11 | let logSubscriber e = 12 | printfn "Hey ho! Lets go!" 13 | printfn "%A" e 14 | printfn "We went!" 15 | 16 | eventStore.AddSubscriber "logsub" logSubscriber 17 | 18 | let random = new Random() 19 | let newRandomInt() = random.Next() 20 | let newAggId() = AggregateId (newRandomInt()) 21 | 22 | 23 | let loanGuid = newRandomInt() 24 | // let userId = UserId (newGuid()) 25 | // let itemId = ItemId (newRandomInt()) 26 | // let libraryId = LibraryId (newGuid()) 27 | // let aggId = AggregateId loanGuid 28 | // let loan = { LoanId = LoanId loanGuid 29 | // UserId = userId 30 | // ItemId = itemId 31 | // LibraryId = libraryId } 32 | // 33 | // let item = ( loan.ItemId, 34 | // Book 35 | // { Title = Title "A book" 36 | // Author = Author "A author"}) 37 | // 38 | // let executer = execute eventStore 39 | // 40 | // let newGuid() = Guid.NewGuid() 41 | // let loanId = LoanId (newGuid()) 42 | // let commandData = LoanItem(loanId, UserId (newGuid()), ItemId (newGuid()), LibraryId (newGuid())) 43 | // let aggId = AggregateId (Guid.NewGuid()) 44 | // 45 | // let result = (aggId, commandData) |> executer 46 | // let returnResult = (aggId, ReturnItem loanId) |> executer 47 | 48 | Console.ReadLine() |> ignore 49 | 0 // return an integer exit code 50 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Contracts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Contracts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/start/LibAAS.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Commands 3 | 4 | type CommandData = 5 | | LoanItem of LoanItem 6 | | ReturnItem of ReturnItem 7 | | RegisterInventoryItem of RegisterInventoryItem 8 | 9 | and LoanItem = { 10 | Id:LoanId 11 | UserId:UserId 12 | ItemId:ItemId 13 | LibraryId:LibraryId } 14 | 15 | and ReturnItem = { 16 | Id:LoanId } 17 | 18 | and RegisterInventoryItem = { 19 | Item:Item 20 | Quantity:Quantity } 21 | 22 | type Command = AggregateId * CommandData 23 | 24 | 25 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Events 3 | open System 4 | 5 | type EventData = 6 | | ItemLoaned of loan:Loan*loanDate:LoanDate*dueDate:DueDate 7 | | ItemRegistered of item:Item * Quantity:Quantity 8 | 9 | type Events = AggregateId * EventData list 10 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Contracts/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Types.fs" 5 | open LibAAS.Contracts.Types 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Contracts.Types 3 | open System 4 | 5 | type AggregateId = AggregateId of int 6 | 7 | type ItemId = ItemId of int 8 | type Title = Title of string 9 | type Author = Author of string 10 | type UserId = UserId of int 11 | type LibraryId = LibraryId of int 12 | type LoanId = LoanId of int 13 | type Book = {Title: Title; Author: Author } 14 | type Quantity = private Quantity of int 15 | with 16 | static member Create x = 17 | if x >= 0 then Quantity x 18 | else raise (exn "Invalid quantity") 19 | type ItemData = 20 | | Book of Book 21 | type Item = ItemId*ItemData 22 | 23 | type LoanDate = LoanDate of DateTime 24 | type DueDate = DueDate of DateTime 25 | 26 | type Loan = { LoanId: LoanId; UserId: UserId; ItemId: ItemId; LibraryId: LibraryId } 27 | 28 | type Version = int 29 | type Error = 30 | | NotImplemented of string 31 | | VersionConflict of string 32 | | InvalidStateTransition of string 33 | | InvalidState of string 34 | | InvalidItem 35 | 36 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Domain/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Domain/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.App.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/start/LibAAS.Domain/CommandHandling.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.CommandHandling 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | 5 | let validateCommand command = command |> ok 6 | 7 | let buildState2 evolveSeed (version, events) = 8 | let evolver res e = bind (evolveSeed.EvolveOne e) res 9 | events |> List.fold evolver (evolveSeed.Init |> ok) 10 | 11 | let stateBuilder evolveSeed getEvents id = 12 | getEvents id >>= buildState2 evolveSeed 13 | 14 | let buildState evolveSeed (aggregateId, version, events, command) = 15 | let evolver res e = bind (evolveSeed.EvolveOne e) res 16 | let state = events |> List.fold evolver (evolveSeed.Init |> ok) 17 | state >>= (fun s -> (aggregateId, version, s, command) |> ok) 18 | 19 | let (|LoanCommand|InventoryCommand|) command = 20 | match command with 21 | | LoanItem _ -> LoanCommand 22 | | ReturnItem _ -> LoanCommand 23 | | RegisterInventoryItem _ -> InventoryCommand 24 | 25 | let commandRouteBuilder stateGetters commandData = 26 | match commandData with 27 | | LoanCommand -> 28 | (buildState Loan.evolveSeed) 29 | >=> (fun (_,_,s,command) -> Loan.executeCommand s stateGetters command) 30 | | InventoryCommand -> 31 | (buildState Inventory.evolveSeed) 32 | >=> (fun (_,_,s,command) -> Inventory.executeCommand s command) 33 | 34 | let executeCommand stateGetters (aggregateId, currentVersion, events, command) = 35 | let (aggId, commandData) = command 36 | (aggregateId, currentVersion, events, command) 37 | |> commandRouteBuilder stateGetters commandData 38 | >>= (fun es -> (aggregateId, currentVersion, es, command) |> ok) 39 | 40 | let getEvents2 eventStore (id) = 41 | eventStore.GetEvents (StreamId id) 42 | 43 | let getEvents eventStore command = 44 | let (AggregateId aggregateId, commandData) = command 45 | eventStore.GetEvents (StreamId aggregateId) 46 | >>= (fun (StreamVersion ver, events) -> (AggregateId aggregateId, ver, events, command) |> ok) 47 | 48 | let saveEvents eventStore (AggregateId aggregateId, expectedVersion, events, command) = 49 | eventStore.SaveEvents (StreamId aggregateId) (StreamVersion expectedVersion) events 50 | 51 | let createGetters eventStore = 52 | { 53 | GetInventoryItem = (fun (ItemId id) -> id |> stateBuilder Inventory.evolveSeed (getEvents2 eventStore)) 54 | } 55 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Domain/DomainEntry.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Domain.DomainEntry 2 | open LibAAS.Domain.CommandHandling 3 | 4 | let execute eventStore command = 5 | let stateGetters = createGetters eventStore 6 | 7 | command 8 | |> validateCommand 9 | >>= getEvents eventStore 10 | >>= executeCommand stateGetters 11 | >>= saveEvents eventStore 12 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Domain/DomainTypes.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.DomainTypes 2 | 3 | open LibAAS.Contracts 4 | 5 | type InventoryState = 6 | | ItemInit 7 | | ItemInStock of item:Item*quantity:Quantity 8 | 9 | type LoanState = 10 | | LoanInit 11 | 12 | type EvolveOne<'T> = EventData -> 'T -> Result<'T, Error> 13 | type EvolveSeed<'T> = 14 | { 15 | Init: 'T 16 | EvolveOne: EvolveOne<'T> 17 | } 18 | 19 | type InternalDependencies = 20 | { 21 | GetItem: ItemId -> Result 22 | } 23 | 24 | type StateGetters = 25 | { 26 | GetInventoryItem: ItemId -> Result 27 | } 28 | 29 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Domain/Inventory.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Inventory 2 | 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainTypes 5 | open System 6 | 7 | let handleAtInit (id, (command:RegisterInventoryItem)) = 8 | [ItemRegistered(command.Item, command.Quantity)] |> ok 9 | 10 | let executeCommand state command = 11 | match state, command with 12 | | ItemInit, (id, RegisterInventoryItem cmd) -> handleAtInit (id, cmd) 13 | | _ -> InvalidState "Inventory" |> fail 14 | 15 | let evolveAtInit = function 16 | | ItemRegistered(item, quantity) -> ItemInStock (item, quantity) |> ok 17 | 18 | let evolveOne (event:EventData) state = 19 | match state with 20 | | ItemInit -> evolveAtInit event 21 | 22 | let evolveSeed = {Init = ItemInit; EvolveOne = evolveOne} -------------------------------------------------------------------------------- /ex4/start/LibAAS.Domain/Loan.fs: -------------------------------------------------------------------------------- 1 | module internal LibAAS.Domain.Loan 2 | open LibAAS.Contracts 3 | open LibAAS.Domain.DomainTypes 4 | open System 5 | 6 | let handleAtInit stateGetters ((aggId:AggregateId), (commandData:LoanItem)) = 7 | commandData.ItemId |> 8 | (stateGetters.GetInventoryItem 9 | >=> function 10 | | ItemInit -> InvalidItem |> fail 11 | | _ -> 12 | let loan = 13 | { LoanId = commandData.Id 14 | UserId = commandData.UserId 15 | ItemId = commandData.ItemId 16 | LibraryId = commandData.LibraryId } 17 | let now = DateTime.Today 18 | [ItemLoaned (loan, LoanDate now, DueDate (now.AddDays(7.)))] |> ok) 19 | 20 | let handleAtCreated data ((aggId:AggregateId), (commandData:ReturnItem)) = 21 | raise (exn "Implement me") 22 | 23 | let executeCommand state stateGetters command = 24 | match state, command with 25 | | LoanInit, (id, LoanItem data) -> handleAtInit stateGetters (id, data) 26 | | _ -> InvalidState "Loan" |> fail 27 | 28 | let evolveAtInit = function 29 | | _ -> raise (exn "Implement me") 30 | 31 | let evolveAtCreated data = function 32 | | _ -> raise (exn "Implement me") 33 | 34 | let evolveOne (event:EventData) state = 35 | match state with 36 | | _ -> raise (exn "Implement me") 37 | 38 | let evolveSeed = {Init = LoanInit; EvolveOne = evolveOne} 39 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Infrastructure/AgentHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module AgentHelper 3 | 4 | type Agent<'T> = MailboxProcessor<'T> 5 | let post (agent:Agent<'T>) message = agent.Post message 6 | let postAsyncReply (agent:Agent<'T>) messageConstr = agent.PostAndAsyncReply(messageConstr) 7 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Infrastructure/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Infrastructure.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/start/LibAAS.Infrastructure/ErrorHandling.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ErrorHandling 3 | 4 | type Result<'TResult, 'TError> = 5 | | Success of 'TResult 6 | | Failure of 'TError 7 | 8 | let bind f = function 9 | | Success y -> f y 10 | | Failure err -> Failure err 11 | 12 | let ok x = Success x 13 | let fail x = Failure x 14 | 15 | let (>>=) result func = bind func result 16 | 17 | let (>=>) f1 f2 = f1 >> (bind f2) -------------------------------------------------------------------------------- /ex4/start/LibAAS.Infrastructure/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open LibAAS.Infrastructure 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /ex4/start/LibAAS.Tests/InventoryTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.InventoryTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Tests.Specification 5 | open LibAAS.Tests.TestHelpers 6 | open Xunit 7 | 8 | module ``When add an item to the inventory`` = 9 | 10 | [] 11 | let ``the item should be added``() = 12 | let itemIntId = newRandomInt() 13 | let aggId = AggregateId itemIntId 14 | let itemId = ItemId itemIntId 15 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 16 | let item = itemId,book 17 | let qty = Quantity.Create 10 18 | 19 | Given defaultPreconditions 20 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 21 | |> Then ([ItemRegistered(item, qty)] |> ok) 22 | 23 | [] 24 | let ``The item should not be added if the id is not unique``() = 25 | let itemIntId = newRandomInt() 26 | let aggId = AggregateId itemIntId 27 | let itemId = ItemId itemIntId 28 | let book = Book {Title = Title "Magic Book"; Author = Author "JRR Tolkien"} 29 | let item = itemId,book 30 | let qty = Quantity.Create 10 31 | 32 | Given { 33 | defaultPreconditions 34 | with 35 | presets = [aggId, [ItemRegistered(item, qty)]] } 36 | |> When (aggId, RegisterInventoryItem { Item = item; Quantity = qty }) 37 | |> Then (InvalidState "Inventory" |> fail) -------------------------------------------------------------------------------- /ex4/start/LibAAS.Tests/LoanTests.fs: -------------------------------------------------------------------------------- 1 | namespace LibAAS.Tests.LoanTests 2 | open System 3 | open LibAAS.Contracts 4 | open LibAAS.Domain 5 | open LibAAS.Tests.Specification 6 | open LibAAS.Tests.TestHelpers 7 | open Xunit 8 | 9 | [] 10 | module LoanTestsHelpers = 11 | let createLoanTestData() = 12 | let loanIntId = newRandomInt() 13 | let userId = UserId (newRandomInt()) 14 | let itemId = ItemId (newRandomInt()) 15 | let libraryId = LibraryId (newRandomInt()) 16 | let aggId = AggregateId loanIntId 17 | aggId, { LoanId = LoanId loanIntId 18 | UserId = userId 19 | ItemId = itemId 20 | LibraryId = libraryId } 21 | 22 | let today() = System.DateTime.Today 23 | 24 | module ``When loaning an item`` = 25 | 26 | [] 27 | let ``the loan should be created and due date set``() = 28 | let aggId,loan = createLoanTestData() 29 | 30 | let item = ( loan.ItemId, 31 | Book 32 | { Title = Title "A book" 33 | Author = Author "A author"}) 34 | 35 | let qty = Quantity.Create 10 36 | let (ItemId id) = loan.ItemId 37 | let itemAggId = AggregateId id 38 | 39 | Given {defaultPreconditions 40 | with 41 | presets = [itemAggId, [ItemRegistered(item, qty)]]} 42 | |> When (aggId, LoanItem { Id = loan.LoanId; UserId = loan.UserId; ItemId = loan.ItemId; LibraryId = loan.LibraryId }) 43 | |> Then ([ItemLoaned 44 | ( loan, 45 | LoanDate System.DateTime.Today, 46 | DueDate (System.DateTime.Today.AddDays(7.)))] |> ok) 47 | 48 | [] 49 | let ``the user shoul be notified if item doesn't exist``() = 50 | let aggId,loan = createLoanTestData() 51 | 52 | Given defaultPreconditions 53 | |> When (aggId, LoanItem { Id = loan.LoanId; UserId = loan.UserId; ItemId = loan.ItemId; LibraryId = loan.LibraryId }) 54 | |> Then (InvalidItem |> fail) 55 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Tests/Specification.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module LibAAS.Tests.Specification 3 | open LibAAS.Contracts 4 | open LibAAS.Domain.DomainEntry 5 | open EventStore 6 | open Swensen.Unquote 7 | 8 | type Precondition = 9 | { presets: Events list } 10 | 11 | type Specification = 12 | { PreCondition:Precondition 13 | Command: Command option 14 | PostCondition: (Result) option } 15 | 16 | let notImplemented = fun _ -> "Dependency not set for test" |> NotImplemented |> fail 17 | 18 | let defaultPreconditions = 19 | { presets = [] } 20 | 21 | let Given preCondition = 22 | { PreCondition = preCondition 23 | Command = None 24 | PostCondition = None } 25 | 26 | let When command spec = {spec with Command = Some command} 27 | 28 | let Then (postCondition: Result) spec = 29 | let finalSpec = {spec with PostCondition = Some postCondition} 30 | let eventStore = createInMemoryEventStore (Error.VersionConflict "Invalid version when saving") 31 | let executer = execute eventStore 32 | 33 | let savePreConditions preCondition = 34 | preCondition 35 | |> List.iter (fun (AggregateId aggId, events) -> 36 | eventStore.SaveEvents (StreamId aggId) (StreamVersion 0) events |> ignore) 37 | 38 | finalSpec.PreCondition.presets |> savePreConditions 39 | let actual = finalSpec.Command |> Option.get |> executer 40 | let expected = finalSpec.PostCondition |> Option.get 41 | 42 | test <@ actual = expected @> 43 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Tests/TestHelpers.fs: -------------------------------------------------------------------------------- 1 | module LibAAS.Tests.TestHelpers 2 | open LibAAS.Contracts.Types 3 | open System 4 | 5 | let random = new Random() 6 | let newRandomInt() = random.Next() 7 | let newAggId() = AggregateId (newRandomInt()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ex4/start/LibAAS.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://nuget.org/api/v2 2 | 3 | nuget FAKE 4.9.3 4 | nuget NuGet.CommandLine 2.8.6 5 | nuget xunit.runner.console 2.1.0 -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | NUGET 2 | remote: https://nuget.org/api/v2 3 | specs: 4 | FAKE (4.9.3) 5 | NuGet.CommandLine (2.8.6) 6 | xunit.runner.console (2.1.0) 7 | --------------------------------------------------------------------------------