Giter Site home page Giter Site logo

rommsen / eventsourcing-diy Goto Github PK

View Code? Open in Web Editor NEW
61.0 61.0 14.0 2.66 MB

Companion repository for my Event Sourcing - Do it yourself video series.

Home Page: https://www.youtube.com/playlist?list=PL-nSd-yeckKh7Ts5EKChek7iXcgyUGDHa

License: MIT License

F# 100.00%
cqrs eventsourcing fsharp youtube

eventsourcing-diy's People

Contributors

alexzeitler avatar rommsen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

eventsourcing-diy's Issues

Use of projections in read models

This is something I burned my fingers on. I used a projection in a read-model and this didn't go well. What happened is that I had these projections:

            let updateRegisteredPatients (set: Set<_>) event =
                match event with
                | Registered pat ->
                    pat |> set.Add
                | _ -> set

            let registerdPatients =
                {
                    Init = Set.empty
                    Update = updateRegisteredPatients
                }

            let admittedPatients events =
                let registered = 
                    events 
                    |> (Projection.project registerdPatients)
                    |> Set.toList

                events
                |> List.fold (fun acc e ->
                    match e with
                    | Admitted hn ->
                        match registered 
                              |> List.tryFind (fun pat -> pat.HospitalNumber = hn) with
                        | Some pat -> acc |> List.append [ pat ]
                        | None -> acc
                    | _ -> acc
                ) []

So, the admittedPatients expects all events to reconstruct which registered patients are admitted. So far so good. But then I used these projections to create readmodels:

            let registered () : ReadModel<_, _> =
                let updateState state evs =
                    evs
                    |> List.fold (Projection.intoMap Projections.registerdPatients) 
                                 state

                ReadModel.inMemory updateState Map.empty


            let admitted () : ReadModel<_, _> =
                let updateState state evs =
                    evs
                    |> Event.asEvents
                    |> Projections.admittedPatients
                    |> List.append state

                ReadModel.inMemory updateState []

And then things fall apart. The read model only receives the new events. So, when a patient is admitted, only the admitted event is passed to the readmodel. This works fine for the registered patients read model, but the admitted patients read model passes these admitted events to the admittedPatients projection that will always return an empty list, as it cannot reconstruct the registered patients.

This was quite hard to detect (meaning it took me more than half an hour). And generally, I try to avoid these kind of errors like the plague. So, my inclination now is to completely separate the event handling and projection logic from the read model logic and not to use projections in readmodels.

Event sourcing versioning

Hi Roman,
I loved the video from F# exchange london from a few weeks ago, thank you for sharing with community.
I appreciate that the code here is a simple demo but perhaps you could give me some pointers on how you approach event versioning assuming that the event is serialized to a datastore and read back at a later date. Any change to the event type will break the deserialisation.
It seems to me that without a strategy to handle this scenario then rolling out changes would be difficult.

Why EventListener and EventStore as a config option

The EventListener and EventStore are dependencies for EventSourced. And I know that in principle dependencies should be parameterized. However, it seems quite unlikely to me that these dependencies will quickly change over time or need to be customizable. Also, it makes the config more complex and more difficult to understand. Without them you have a config like:

    type EventSourcedConfig<'Comand,'Event,'Query> =
        {
            EventStorageInit : unit -> EventStorage<'Event>
            CommandHandlerInit : EventStore<'Event> -> CommandHandler<'Comand>
            QueryHandler : QueryHandler<'Query>
            EventHandlers : EventHandler<'Event> list
        }

Which to me seems more relevant and easier to understand.

See: halcwb/InformedICU@1b7b62d

Agent doesn't really handle exceptions

I have been looking at the Agent implementation of the MailboxProcessor:

        let inbox = new MailboxProcessor<'T>(fun _ ->
            async {
              // Run the user-provided function & handle exceptions
              try return! f self
              with e -> errorEvent.Trigger(e)
            })

I noticed that in your EventStore you still need to wrap your code in a try with construct. Otherwise the error will indeed reach the Agent code, but this will only propagate the error to error listeners, but the agent will still 'die'.

How to solve this, I haven't figured out as the implementation needs to recurse on the start function to stay alive.

Another event sourcing use case

I just created another use case of event sourcing. The nice thing is, that this can be an extremely useful app, literally saving lives. I posted a blog about it and have a repository for the source code.

If you are interested let me know.

Adding truck 2 with wrong source

In your code you add truck 2 with the wrong source:
("Add_truck_to_fleet truck2", fun () -> Add_truck_to_fleet truck2 |> app.HandleCommand truck1_guid |> runAsync |> printCommandResults "Command")

As a side note, this is also what I am struggling a bit with. It seems that the infrastructure is leaking through the code and that this results in errors like the one above which are quite difficult to recognize. In this example the HandleCommand depends on the eventsource, but the eventsource as a domain concept has no particular meaning. Rather you would like to be able to just add the truck to the fleet.

Also, what about nested aggregates? For example the truck gets lists of orders that consists of different flavors. Then the truck is the top level domain entity for orders and each order is a top level domain entity for the individual flavor lines.

Use case of event sourcing

I am writing a use case for your event store implementation. I find the whole Event store and CQRS set up fascinating and I think that the setting of a medical application is extremely well suited for such an approach.

What I first try to do is put the whole app in a script file so I can mess around with it and get a feel of how it works. Only when the app is sufficiently finished I will start to migrate this to a proper project structure.

Add truck 2 to fleet

There is a typo on 86 for adding truck 2 to the fleet in Program.fs. The app.HandleCommand truck1_guid should be app.HandleCommand truck2_guid

Performance

I noticed that command handling of a lot of commands (> 1000) at the same time on a stream that contains a large number of events (> 10000) results in poor performance. When running a really large amount of commands, the system gets really slow.

I used this test code:


            type Command = HelloWordCommand of string

            type Event = HelloWordEvent of string

            let evs =
                HelloWordEvent "Init"
                |> List.replicate 10000
                |> Event.enveloped "helloworld"

            // create an event store for the hello world event
            let store : EventStore<Event> = 
                EventStorage.InMemoryStorage.initialize evs
                |> EventStore.initialize

            let behaviour : Behaviour<Command, Event> =
                fun cmd ees ->
                    match cmd with
                    | HelloWordCommand s -> 
                        HelloWordEvent s
                        |> List.replicate 2

            let handler =
                initialize behaviour store

            let run n =
                let streamId = "helloworld"
                
                for i in [1..n] do
                    "Hello World"
                    |> HelloWordCommand
                    |> handler.Handle streamId
                    |> Async.RunSynchronously
                    |> ignore

If you run 500 times, you'll get:

run 500;;
Real: 00:00:06.368, CPU: 00:00:08.218, GC gen0: 425, gen1: 184, gen2: 7
val it : unit = ()

I also tried this with your demo app, and the same happened. I did not expect this. I thought this would instead be blazing fast as everything runs in memory.

P.S. I know you're code isn't intended as production code, but I am curious. Also, I think F# should have a decent event store library, instead of using a C# based library (ugh ;)).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.