Show me an implementation of an Organisation Aggregate in F# that can handle "Register", "Suspend" and "ChangeAddress" commands
Here is a simple implementation of an organization aggregate in F# that can handle the "Register", "Suspend", and "ChangeAddress" commands:
type Address = {
Street: string
City: string
State: string
ZipCode: string
}
type OrganizationState =
| Registered of Address
| Suspended of Address
type OrganizationId = string
type Organization = {
Id: OrganizationId
State: OrganizationState
}
type OrganizationCommand =
| Register of Address
| Suspend
| ChangeAddress of Address
type OrganizationEvent =
| Registered of OrganizationId * Address
| Suspended of OrganizationId * Address
| AddressChanged of OrganizationId * Address
let handleCommand (org: Organization) (cmd: OrganizationCommand) =
match org.State, cmd with
| Registered _, Register _ ->
// Cannot register an already registered organization
[]
| Suspended _, Register address ->
[ Registered (org.Id, address) ]
| Registered address, Suspend ->
[ Suspended (org.Id, address) ]
| Suspended _, Suspend ->
// Cannot suspend an already suspended organization
[]
| Registered address, ChangeAddress newAddress ->
[ AddressChanged (org.Id, newAddress) ]
| Suspended address, ChangeAddress newAddress ->
[ AddressChanged (org.Id, newAddress) ]
let applyEvent (org: Organization) (evt: OrganizationEvent) =
match evt with
| Registered (_, address) ->
{ org with State = Registered address }
| Suspended (_, address) ->
{ org with State = Suspended address }
| AddressChanged (_, address) ->
{ org with State = Registered address }
In this implementation, we define an OrganizationState discriminated union with two cases: Registered and Suspended. We also define an OrganizationId and Organization type to represent the unique identifier and state of an organization, respectively. The OrganizationCommand type represents the possible commands that can be executed on an organization, while the OrganizationEvent type represents the events that result from executing these commands.
The handleCommand function takes an Organization and a OrganizationCommand as input and returns a list of OrganizationEvents, which represent the changes that result from executing the command. The applyEvent function takes an Organization and an OrganizationEvent as input and returns a new Organization with its state updated based on the event.
Now please write GWT unit tests for this aggregate
Here is an example of how you could write unit tests for the organization aggregate using the F# unit testing framework, FsUnit:
#r "FsUnit.dll"
open FsUnit
[<Test>]
let ``registering a new organization should produce a Registered event``() =
let address = { Street = "1 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let org = { Id = "123"; State = Suspended address }
let events = handleCommand org (Register address)
events |> should equal [ Registered ("123", address) ]
[<Test>]
let ``suspending an already registered organization should produce a Suspended event``() =
let address = { Street = "1 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let org = { Id = "123"; State = Registered address }
let events = handleCommand org Suspend
events |> should equal [ Suspended ("123", address) ]
[<Test>]
let ``changing the address of a registered organization should produce an AddressChanged event``() =
let oldAddress = { Street = "1 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let newAddress = { Street = "2 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let org = { Id = "123"; State = Registered oldAddress }
let events = handleCommand org (ChangeAddress newAddress)
events |> should equal [ AddressChanged ("123", newAddress) ]
[<Test>]
let ``changing the address of a suspended organization should produce an AddressChanged event``() =
let oldAddress = { Street = "1 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let newAddress = { Street = "2 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let org = { Id = "123"; State = Suspended oldAddress }
let events = handleCommand org (ChangeAddress newAddress)
events |> should equal [ AddressChanged ("123", newAddress) ]
[<Test>]
let ``applying a Registered event should change the state of the organization to Registered``() =
let address = { Street = "1 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let org = { Id = "123"; State = Suspended address }
let evt = Registered ("123", address)
let updated = applyEvent org evt
updated.State |> should equal (Registered address)
[<Test>]
let ``applying a Suspended event should change the state of the organization to Suspended``() =
let address = { Street = "1 Main St"; City = "San Francisco"; State = "CA"; ZipCode = "94111" }
let org = { Id = "123"; State = Registered address }
let evt = Suspended ("123", address)
let updated = applyEvent org evt
updated.State |> should equal (Suspended address)
Can you make them property-based tests? For property-based tests auto-generate input data
Here is an updated version of the previous code, where the input data is automatically generated using FsCheck:
#r "FsCheck.dll"
open FsCheck
open FsCheck.Xunit
[<Property(QuietOnSuccess=true)>]
let ``registering a new organization should produce a Registered event``() =
let address = Arb.generate<Address>()
let org = { Id = "123"; State = Suspended address }
let events = handleCommand org (Register address)
events = [ Registered ("123", address) ]
events |> Prop.forAll
[<Property(QuietOnSuccess=true)>]
let ``suspending an already registered organization should produce a Suspended event``() =
let address = Arb.generate<Address>()
let org = { Id = "123"; State = Registered address }
let events = handleCommand org Suspend
events = [ Suspended ("123", address) ]
events |> Prop.forAll
[<Property(QuietOnSuccess=true)>]
let ``changing the address of a registered organization should produce an AddressChanged event``() =
let oldAddress = Arb.generate<Address>()
let newAddress = Arb.generate<Address>()
let org = { Id = "123"; State = Registered oldAddress }
let events = handleCommand org (ChangeAddress newAddress)
events = [ AddressChanged ("123", newAddress) ]
events |> Prop.forAll
[<Property(QuietOnSuccess=true)>]
let ``changing the address of a suspended organization should produce an AddressChanged event``() =
let oldAddress = Arb.generate<Address>()
let newAddress = Arb.generate<Address>()
let org = { Id = "123"; State = Suspended oldAddress }
let events = handleCommand org (ChangeAddress newAddress)
events = [ AddressChanged ("123", newAddress) ]
events |> Prop.forAll
[<Property(QuietOnSuccess=true)>]
let ``applying a Registered event should change the state of the organization to Registered``() =
let address = Arb.generate<Address>()
let org = { Id = "123"; State = Suspended address }
let evt = Registered ("123", address)
let updated = applyEvent org evt
updated.State = Registered address
updated.State |> Prop.forAll
[<Property(QuietOnSuccess=true)>]
let ``applying a Suspended event should change the state of the organization to Suspended``() =
let address = Arb.generate<Address>()
let org = { Id = "123"; State = Registered address }
let evt = Suspended ("123", address)
let updated = applyEvent org evt
updated.State = Suspended address
updated.State |> Prop.forAll
Please use Hedgehog property-based testing framework
Here's an example of how you could write property-based tests for the organization aggregate using the Hedgehog property-based testing framework in F#:
#r "Hedgehog.dll"
open Hedgehog
open Hedgehog.Gen
open Hedgehog.Range
let registeredProp = property {
let! address = genAddress
let org = { Id = "123"; State = Suspended address }
let events = handleCommand org (Register address)
return events = [ Registered ("123", address) ]
}
let suspendedProp = property {
let! address = genAddress
let org = { Id = "123"; State = Registered address }
let events = handleCommand org Suspend
return events = [ Suspended ("123", address) ]
}
let addressChangedProp = property {
let! oldAddress = genAddress
let! newAddress = genAddress
let org = { Id = "123"; State = Registered oldAddress }
let events = handleCommand org (ChangeAddress newAddress)
return events = [ AddressChanged ("123", newAddress) ]
}
let registeredEventProp = property {
let! address = genAddress
let org = { Id = "123"; State = Suspended address }
let evt = Registered ("123", address)
let updated = applyEvent org evt
return updated.State = Registered address
}
let suspendedEventProp = property {
let! address = genAddress
let org = { Id = "123"; State = Registered address }
let evt = Suspended ("123", address)
let updated = applyEvent org evt
return updated.State = Suspended address
}
let addressChangedEventProp = property {
let! oldAddress = genAddress
let! newAddress = genAddress
let org = { Id = "123"; State = Registered oldAddress }
let evt = AddressChanged ("123", newAddress)
let updated = applyEvent org evt
return updated.State = Registered newAddress
}
[<Test>]
let ``registering a new organization should produce a Registered event``() =
Check.Quick registeredProp
[<Test>]
let ``suspending an already registered organization should produce a Suspended event``() =
Check.Quick suspendedProp
[<Test>]
let ``changing the address of a registered organization should produce an AddressChanged event``() =
Check.Quick addressChangedProp
[<Test>]
let ``applying a Registered event should change the state of the organization to Registered``() =
Check.Quick registeredEventProp
[<Test>]
let ``applying a Suspended event should change the state of the organization to Suspended``() =
Check.Quick suspendedEventProp
[<Test>]
let ``applying an AddressChanged event should change the address of the organization``() =
Check.Quick addressChangedEventProp