[HN Gopher] The Dual Nature of Events in Event-Driven Architecture
___________________________________________________________________
The Dual Nature of Events in Event-Driven Architecture
Author : lutzh
Score : 103 points
Date : 2024-10-31 08:15 UTC (2 days ago)
(HTM) web link (www.reactivesystems.eu)
(TXT) w3m dump (www.reactivesystems.eu)
| lutzh wrote:
| Article observing that events in event-driven architecture are
| both triggers of actions and carriers of data, and that these
| roles may conflict in the event design. Submitted by author.
| gunnarmorling wrote:
| Nice one. I wrote about this a while ago, from a slightly
| different perspective, focusing on data change events [1].
| Making a similar differentiation there between id-only events
| (which you describe as triggers of action; from a data change
| feed perspective, that action typically would be a re-select of
| the current state of the represented record), full events (your
| carriers of data) and patch events (carriers of data with only
| the subset of attributes whose value has changed).
|
| [1] https://www.decodable.co/blog/taxonomy-of-data-change-
| events
| lutzh wrote:
| Thanks for your feedback Gunnar, I appreciate it!
|
| Your categorization makes total sense and fits well with what
| I called the "spectrum". I only mentioned the "id-only"
| events to show what the one end of the spectrum would look
| like. What I call the "trigger" events would be what you call
| "delta" events. I should have written that more clearly.
|
| Interestingly a few people advocated for id-only events as a
| response to the article. I have some issues with that
| pattern.. already thinking about a follow-up article to
| elaborate on that.
| bob1029 wrote:
| The triggering of the action is a direct consequence of the
| information an event contains. Whether or not an action is
| triggered should not be the responsibility of the event.
|
| If you are writing events with the intention of having them
| invoke some specific actions, then you should prefer to invoke
| those things directly. You should be describing a space of things
| that have occurred, not commands to be carried out.
|
| By default I would only include business keys in my event data.
| This gets you out of traffic on having to make the event serve as
| an aggregate view for many consumers. If you provide the keys of
| the affected items, each consumer can perform their own targeted
| lookups as needed. Making assumptions about what views each will
| need is where things get super nasty in my experience (i.e.
| modifying events every time you add consumers).
| withinboredom wrote:
| Events shouldn't carry any data in my opinion, except
| parameterized data. In the context of a booking, for example,
| it would be SeatBooked {41A} instead of 41ABooked, though the
| latter is a better event, but harder to program for. The entire
| flow might looked like this:
|
| SeatTimeLimitedReserved {41A, 15m}
|
| SeatAssignedTo {UserA}
|
| SeatBooked {41A}
|
| If a consumer needs more data, there should be a new event.
| lutzh wrote:
| Thanks for your feedback, I appreciate it!
|
| > The triggering of the action is a direct consequence of the
| information an event contains. Whether or not an action is
| triggered should not be the responsibility of the event.
|
| I agree, but still for different consumers events will have
| different consequences - in some consumers it'll trigger an
| action that is part of a higher-level process (and possibly
| further events), in others it'll only lead to data being
| updated.
|
| > If you are writing events with the intention of having them
| invoke some specific actions, then you should prefer to invoke
| those things directly. You should be describing a space of
| things that have occurred, not commands to be carried out.
|
| With this I don't agree. I think that's the core of event-
| driven architecture that events drive the process, i.e. will
| trigger certain actions. That's not contradicting them
| describing what has occurred, and doesn't make them commands.
|
| > By default I would only include business keys in my event
| data. This gets you out of traffic on having to make the event
| serve as an aggregate view for many consumers. If you provide
| the keys of the affected items, each consumer can perform their
| own targeted lookups as needed. Making assumptions about what
| views each will need is where things get super nasty in my
| experience (i.e. modifying events every time you add
| consumers).
|
| This is feedback I got multiple times, the "notification plus
| callback" seems to be a popular pattern. It has its own
| problems though, both conceptual (event representing an
| immutable set of facts) and technical (high volume of events).
| I think digging into the pros and cons of that pattern will be
| one of my next blog posts! Stay tuned!
| williamdclt wrote:
| > each consumer can perform their own targeted lookups as
| needed
|
| that puts you into tricky race condition territory, the data
| targeted by an event might have changed (or be deleted) between
| the time it was emitted and the time you're processing it. It's
| not always a problem, but you have to analyse if it could be
| every time.
|
| It also means that you're losing information on what this event
| actually represents: looking at an old event you wouldn't know
| what it actually did, as the data has changed since then.
|
| It also introduces a synchronous dependency between services:
| your consumer has to query the service that dispatched the
| event for additional information (which is complexity and extra
| load).
|
| Ideally you'd design your event so that downstream consumers
| don't need extra information, or at least the information they
| need is independent from the data described by the event: eg a
| consumer needs the user name to format an email in reaction to
| the "user_changed_password" event? No problem to query the
| service for the name, these are independent concepts, updates
| to these things (password & name) can happen concurrently but
| it doesn't really matter if a race condition happens
| candiddevmike wrote:
| There should be some law that says strictly serialized
| process should never be broken into discreet services.
| Distributed locks and transactions are hell.
| withinboredom wrote:
| The best way to avoid distributed locks and transactions is
| to manually do the work. For example, instead of doing a
| distributed lock on two accounts when transferring funds,
| you might do this (which is the same as a distributed
| transaction, without the lock):
|
| 1. take money from account A
|
| 2. if failed, put money back into account A
|
| 3. put money into account B
|
| 4. if failed, put money back into account A
|
| In other words, perform compensating actions instead of
| doing transactions.
|
| This also requires that you have some kind of mechanism to
| handle an application crash between 2 and 3, but that is
| something else entirely. I've been working on this for a
| couple of years now and getting close to something really
| interesting ... but not quite there yet.
| candiddevmike wrote:
| > This also requires that you have some kind of mechanism
| to handle an application crash between 2 and 3, but that
| is something else entirely
|
| Like a distributed transaction or lock. This is the
| entire problem space, your example above is very naive.
| nyrikki wrote:
| It is not a DTC, despite the DB world using ATM's wrongly
| as an example for decades, follow their model for
| actually moving money and you would be sent to jail.
|
| "Accountants don't use erasers"
|
| The ledger is the source of truth in accounting, if you
| use event streams as the source of truth you can gain the
| same advantages and disadvantages.
|
| An event being past tense ONLY is a very critical part.
|
| There are lots of way to address this all with their own
| tradeoffs and it is a case of the least worst option for
| a particuar context.
|
| but over-reliance on ACID DBMSs and the false claim that
| ATMs use DTC really does hamper the ability to teach
| these concepts.
| withinboredom wrote:
| You _can_ use them, but even that won't save you. If you
| take a distributed lock, and crash, you still have the
| same issue. What I wrote is essentially a distributed
| transaction and what happens in a distributed transaction
| with "read uncommitted" isolation levels. A database that
| supports this handles all the potential failure cases for
| you. However, that doesn't magically make the errors
| disappear or may not be even fully/properly handled
| (e.g., a node running out of disk space in the middle of
| the transaction) by your code/database. It isn't naive,
| it is literally a pseudo-code of what you are proposing.
| singron wrote:
| The better version of this is sagas, which is a kind of a
| simplified distributed transaction. If you do this
| without actually using sagas, you can really mess this
| up.
|
| E.g. you perform step 2, but fail to record it. When
| resuming from crash, you perform step 2 again. Now A has
| too much money in their account.
| withinboredom wrote:
| Sagas are great for this and should be used when able,
| IMHO. It's still possible to mess it up, as there are
| basically two guarantees you can make in a distributed
| system: at-least-once, and at-most-once. Thus, you will
| either need to accept the possibility of lost messages or
| be able to make your event consumers idempotent to
| provide an illusion of exactly-once.
|
| Sagas require careful consideration to make sure you can
| provide one of these guarantees during a "commit" (the
| order in which you ACK a message, send resulting
| messages, and record your own state -- if necessary) as
| these operations are non-atomic. If you mess it up, you
| can end up providing the wrong level of guarantee by
| accident. For example:
|
| 1. fire resulting messages
|
| 2. store state (which includes ids of processed messages
| for idempotency)
|
| 3. ACK original event
|
| In this case, you guarantee that you will always send
| results at-least-once if a crash happens between 1&2.
| Once we get past 2, we provide exactly-once semantics,
| but we can only guarantee at-least-once. If we change the
| order to:
|
| 1. store state
|
| 2. fire messages
|
| 3. ACK original message
|
| We now only provide at-most-once semantics. In this case,
| if we crash between 1&2, when we resume, we will see that
| we've already handled the current message and not process
| it again, despite never having sent any result yet. We
| end up with at-most-once if we swap 1&3 as well.
|
| So, yes, Sagas are great, but still pretty easy to mess
| up.
| hcarvalhoalves wrote:
| Here's how you can do this. You have 3 accounts: A, B and
| in-flight.
|
| 1. Debit A, Credit in-flight.
|
| 2. Credit B, Debit in-flight.
|
| If 1. fails, nothing happened and everything is
| consistent.
|
| If 2. fails, you know (because you have money left on in-
| flight), and you can retry later, or refund A.
|
| This way at no point your total balance decreases, so
| everything is always consistent.
| chipdart wrote:
| > Distributed locks and transactions are hell.
|
| Which distributed transaction scenario have you ever dealt
| with that wasn't correctly handled by a two-phase commit or
| at worst a three-phase commit?
| marcosdumay wrote:
| Your event data must not be mutable.
|
| That's kind of the first rule of any event-based system. It
| doesn't really matter the architecture, if you decide to name
| the things "event", everybody's head will break if you make
| them mutable.
|
| If you decide to add mutation there in some way, you will
| need to rewrite the event stream, replacing entire events.
| gunnarmorling wrote:
| It's not about mutability of events, but about mutating the
| underlying data itself. If the event only says "customer
| 123 has been updated", and a consumer of that event goes
| back to the source of the event to query the full state of
| that customer 123, it may have been updated again (or even
| deleted) since the event was emitted. Depending on the use
| case, this may or may not be a problem. If the consumer is
| only interested in the current state of the data, this
| typically is acceptable, but if it is needed in the
| complete history of changes, it is not.
| marcosdumay wrote:
| Making a wacky 2-steps announcement protocol doesn't
| change the nature of your events.
|
| If the consumer goes to your database and asks "what's
| the data for customer 123 at event F52A?" it better
| always get back the same data or "that event doesn't
| exist, everything you know is wrong".
| gunnarmorling wrote:
| > ... at event F52A
|
| Sure, if the database supports this sort of temporal
| query, then you're good with such id-only events. But
| that's not exactly the default for most databases / data
| models.
| marcosdumay wrote:
| I'm understanding what you have isn't really "events",
| but some kind of "notifications".
|
| Events are part of a stream that define your data. The
| stream doesn't have to be complete, but if it doesn't
| make sense to do things like buffer or edit it, it's
| probably something else and using that name will mislead
| people.
| chipdart wrote:
| > (...) and a consumer of that event goes back to the
| source of the event to query the full state of that
| customer 123, it may have been updated again (or even
| deleted) since the event was emitted.
|
| So the entity was updated. What's the problem?
| phi-go wrote:
| I understand gp to say that the database data is changed
| not the data in the event.
|
| Surely, some data needs to change if a password is updated?
| bob1029 wrote:
| > an event might have changed (or be deleted) between the
| time it was emitted
|
| Then I would argue it isn't a meaningful event. If some
| attributes of the event could become "out of date" such that
| the logical event risks invalidation in the future, you have
| probably put too much data into the event.
|
| For example, including a user's preferences (e.g., display
| name) in a logon event - while convenient - means that if
| those preferences ever change, the event is invalid to
| reference for those facts. If you only include the user's id,
| your event should be valid forever (for most rational
| systems).
|
| > your consumer has to query the service that dispatched the
| event
|
| An unfortunate but necessary consequence of integrating
| multiple systems together. You can't take out a global
| database lock for every event emitted.
| jwarden wrote:
| Another architecture might be that the service responsible for
| Seat Selection emits a `SeatSelected` event, and another
| service responsible for updating bookings emits a
| `BookingUpdated(Reason: SeatSelected)` "fat" event. Same for
| `PaymentReceived` and `TicketIssued`.
|
| Both events would "describe a space of things that occurred" as
| @bob1029 suggests.
|
| The seat selection process for an actual airline probably needs
| to be more involved. @withinboredom recommends:
| - SeatTimeLimitedReserved {41A, 15m} - SeatAssignedTo
| {UserA} - SeatBooked {41A}
|
| In which case, only SeatBooked would trigger a BookingUpdated
| event.
| lutzh wrote:
| Thanks for your feedback. I realize I should have elaborated
| the example a bit more, it's too vague. So, as I wrote in
| some other reply as well, please don't over-interpret it. The
| point was only to say that in order to differentiate the
| events, we don't necessarily need distinct types (which would
| result in multiple schemas on a topic), but can instead
| encode it in one type/schema. Like mapping in ORM - instead
| of "table per subclass", you can use "table per class
| hierarchy".
| monksy wrote:
| Say that again louder for people in the back.
|
| Message queues aren't a networking protocol. Anyone can
| subscribe to consume the events.
| svilen_dobrev wrote:
| like, events vs documents, ~2007:
|
| https://learn.microsoft.com/en-gb/archive/blogs/nickmalik/ki...
| lutzh wrote:
| Good catch! Indeed, without me realizing it, this trigger/data
| duality is pretty much what Event Message and Document Message
| are in "Enterprise Integration Patterns" (which is what the
| post you linked refers to). As it happens, in the book the
| authors also speak about "a combined document/event message",
| which is how me mostly use events in EDA today, I think.
| revskill wrote:
| Event is a point in time. State is a range in time.
|
| Geometrically speaking.
|
| So, what should be in an event ? To me, it's the minimum but
| sufficient data on its own to be understandable.
| exabrial wrote:
| Well put!
|
| We do a lot of event driven architecture with ActiveMQ. We try to
| stick messaging-as-signalling rather than messaing-as-data-
| transfer. These are the terms we came up with, I'm sure Martin
| Fowler or someone else has described it better!
|
| So we have SystemA that is completing some processing of
| something. It's going to toss a message onto a queue that SystemB
| is listening on. We use an XA to make sure the database and
| broker commit together _1. SystemB then receives the event from
| the queue and can begin it 's little tiny bit of business
| processing.
|
| If one divides their "things" up into logical business units of
| "things that must all happen, or none happen" you end up with a
| pretty minimalistic architecture thats easy to understand but
| also offers retry capabilities if a particular system errors out
| on a single message.
|
| It also allows you to take SystemB offline and let it's work pile
| up, then resume it later. Or you can kick of arbitrary events to
| test parts of the system.
|
| _1: although if this didn't happen, say during a database failure
| at just the right time, the right usage of row locking,
| transactions, and indexes on the database prevent duplicates.
| This is so rare in practice but we protect against it anyway.
| magicalhippo wrote:
| > We try to stick messaging-as-signalling rather than messaing-
| as-data-transfer.
|
| I was thinking of trigger-messages vs data-messages.
|
| Then again we've just begun dabbling with AMQP as part of
| transitioning our legacy application to a new platform, so a
| n00b in the field.
|
| We do have what you might consider hybrid messages, where we
| store incoming data in an object store and send a trigger-
| message with the key to process the data. This keeps the queue
| lean and makes it easy for support to inspect submitted data
| after it's been processed, to determine if we have a bug or
| it's garbage in garbage out.
| remram wrote:
| So... you're _not_ using event-driven architecture. That 's
| fine, but I don't know why it supports this article's weird
| point about event-driven architecture.
| kaba0 wrote:
| I am going on a bit of a tangent here, but I always wondered, are
| those of you who use absolutely huge event-driven architectures,
| have you ever got yourself into a loop? I can't help but worry
| about such, as event systems are fundamentally Turing-complete,
| and with a complex enough system it doesn't seem too hard to
| accidentally send an event because A, which will eventually,
| multiple other events later again cases A.
|
| Is it a common occurence' and if it happens is it hard to
| debug/fix? Does Kafka and other popular event systems have
| something to defend against it?
| lutzh wrote:
| Reg. the "technical" question: Kafka or any log-based message
| broker (or any message queue) would not prevent you from that.
| Any service can publish/send and/or subscribe/receive.
|
| Regarding if it's a problem or a regular occurrence: No, really
| not. I have never seen this being a problem, I think that fear
| is unfounded.
| Hilift wrote:
| Debugging Windows events is challenging. I once created a
| reproducible deadlock using events. I had a Windows application
| with a notebook computer base, so I had to provide for and
| handle the power save/resume event for a service in the
| application. This was simple, just ensure that the service
| stopped and didn't get into a funky state, and restarted
| cleanly after resume. During testing, I accidentally discovered
| the power save event was going into a loop or hanging.
| Fortunately, Microsoft has a tool for this (DebugDiag - C#),
| and to my amazement landed right on the problem area. This
| event (like most) can and was firing multiple times..., so had
| to add some extra locks. Also, the power save/resume code
| worked 100% on a virtual, it was real hardware where the issue
| manifested.
| plaguuuuuu wrote:
| Never seen it, but totally possible.
|
| It always bothers me that the systems I've worked on have their
| data flows mapped out basically in semi-up-to-date miro
| diagrams. If that. There's no overarching machine readable and
| verifiable spec.
| hcarvalhoalves wrote:
| Yes, it happens. A way to deal with it is carrying some counter
| on the message metadata and incrementing it every time a
| consumer passes it along, so you can detect recursions. Another
| is having messages carry a unique id, and consumers record
| already seen messages.
| switch007 wrote:
| Do you consider it a requirement for every message?
|
| Like, the problem sounds bad enough to warrant it. If not,
| now do you choose when to apply it?
|
| Our architects have a habit of ignoring these kind of issues
| and when you suggest making things like this a requirement
| they accuse you of excessive concern!
| dkarl wrote:
| Events are published observations of facts. If you want to be
| able to use them as triggers, or to build state, then you have to
| choose the events and design the system to make that possible,
| but you always have to ensure that systems only publish facts
| that they have observed.
|
| Most issues I've seen with events are caused by giving events
| imprecise names, names that mean more or less than what the
| events attest to.
|
| For example, a UI should not emit a SetCreditLimitToAGazillion
| event because of a user interaction. Downstream programmers are
| likely to get confused and think that the state of the user's
| credit limit has been set to a gazillion, or needs to be set to a
| gazillion. Instead, the event should be
| UserRequestedCreditLimitSetToAGazillion. That accurately
| describes what the UI observed and is attesting to, and it is
| more likely to be interpreted correctly by downstream systems.
|
| In the article's example, SeatSelected sound ambiguous to me.
| Does it only mean the user saw that the seat was available and
| attempted to reserve it? Or does it mean that the system has
| successfully reserved the seat for that passenger? Is the update
| finalized, or is the user partway through a multistep process
| that they might cancel before confirming? Depending on the
| answer, we might need to release the user's prior seat for other
| passengers, or we might need to reserve both seats for a few
| minutes, pending a confirmation of the change or a timeout of
| their hold on the new seat. The state of the reservation may or
| may not need to be updated. (There's nothing wrong with using a
| name like that in a toy example like the article does, but I want
| to make the point that event names in real systems need to be
| much more precise.)
|
| Naming events accurately is the best protection against a
| downstream programmer misinterpreting them. But you still need to
| design the system and the events to make sure they can be used as
| intended, both for triggering behavior and for reporting the
| state and history of the system. You don't get anything
| automatically. You can't design a set of events for triggering
| behavior and expect that you'll be able to tell the state of the
| system from them, or vice-versa.
| lutzh wrote:
| Thanks for your feedback! Very good point on the naming. fwiw
| the idea was if you buy a cinema ticket, you are usually
| presented with some sort of seating plan and select the seat
| (basically putting them into the shopping cart). So
| SeatSelected would be the equivalent of "ItemAdded" to the
| shopping cart in an e-commerce application I guess. Please
| don't over-interpret the example. There isn't even a definition
| what that booking aggregate contains. The point was really only
| to say that in order to differentiate the events, we don't
| necessarily need distinct types (which would result in multiple
| schemas on a topic), but can instead encode it in one
| type/schema. Think of it like mapping in ORM - instead of
| "table per subclass", you can use "table per class hierarchy".
| switch007 wrote:
| Such an important point about naming things accurately. It can
| be hard, so we often take the easy path
|
| But bad names manifest as a multitude of problems much later
| on.
|
| I wonder if this is an area LLMs can help us with because
| really a lot of us do struggle with it. I'm going to
| investigate!
| hcarvalhoalves wrote:
| The "produce a trigger event then have the consumer reach back to
| the producer and fetch data" can be an anti-pattern. You expose
| yourself to all kinds of race conditions and cache incoherency,
| then you start trying to fix with cache invalidation or pinning
| readers, and the result is you can't scale readers well.
|
| If you're a using a message queue, the message should convey
| necessary information such that, if all messages were replayed,
| the consumer would reach the same state. Anything other than
| that, you'll be in a world of pain at scale.
| chipdart wrote:
| > If you're a using a message queue (...)
|
| Events and messages are entirely different things. They might
| look similar, but their responsibilities are completely
| different. The scenario you're describing matches the usecases
| for messages, not events.
| hcarvalhoalves wrote:
| The situation of messages vs events is analogous to append-
| only database vs update-in-place database. You get exposed to
| the same issues at scale if you rely on the later.
|
| Being notified only _when_ something happened isn't always
| useful the world is changing underneath you (it _can be_
| useful in particular situations, when you know state is
| final, but not as a general architecture principle).
| chipdart wrote:
| > The situation of messages vs events is analogous to
| append-only database vs update-in-place database. You get
| exposed to the same issues at scale if you rely on the
| later.
|
| Not really. Messages vs events is a foundational design
| trait whose discussion involves topics such as adopting
| distinct messaging patterns such as message queues or pub-
| sub. They have completely different responsibilities and
| solve completely different problems.
| hcarvalhoalves wrote:
| I don't see how using pub-sub or not changes how you
| model the data. It should be orthogonal. Do you have a
| good example?
| stevenalowe wrote:
| Thank you for this well-written, well-reasoned, and thoroughly
| enjoyable article!
|
| Yet I am troubled by it, and must disagree with some of the
| premises and conclusions. Only you know your specific
| constraints, so my 'armchair architecting' may be way off target.
| If so, I apologize, but I was particularly disturbed by this
| statement:
|
| "events that travel between services have a dual role: They
| trigger actions and carry data."
|
| Yes, sort of, but mostly no. Events do not "trigger" anything.
| The recipient of an event may perform an action in response to
| the event, but events cannot know how they will be used or by
| whom. Every message carries data, but a domain event is
| specifically constrained to be an immutable record of the fact
| that something of interest in the domain has happened.
|
| The notion of modeling 'wide' vs 'short' events seems to ignore
| the domain while conflating very different kinds of messages -
| data/documents/blobs, implementation-level/internal events,
| domain events, and commands.
|
| Modeling decisions should be based on the domain, not wide vs
| short. Domain events should have names that are meaningful in the
| problem domain, and they should not contain extraneous data nor
| references to implementation details/concepts. This leads to a
| few suggestions:
|
| * Avoid Create/Read/Update/Delete (CRUD) event names as these are
| generic implementation-level events, not domain events. Emit such
| events "under the hood" for replication/notification if you must,
| but keep them out of the domain model.
|
| * Name the domain event specifically; CRUD events are generally
| undesirable because they (a) are an implementation detail and (b)
| are not specific enough to understand without more information.
| Beware letting an implementation decision or limitation corrupt
| the domain model. In this example, the BookingUpdated event adds
| no value/information, makes filtering for the specific event
| types more complex, and pollutes the domain language with an
| unnecessary and potentially fragile implementation detail (the
| name of the db table Booking, which could just as easily have
| been Reservation or Order etc). SeatSelected is a great domain
| event name for a booking/reservations system. BookingSeatSelected
| if there is further scope beyond Booking that might have similar
| event names. BookingUpdated is an implementation-level, internal
| event, not part of the problem domain.
|
| * What data is necessary to accurately record this domain event?
| A certain minimal set of relevant data items will be required to
| capture the event in context. Trying to anticipate and shortcut
| the needs of other/future services by adding extraneous data is
| risky. Including a full snapshot of the object even more risky,
| as this makes all consumers dependent on the entire object
| schema.
|
| * The notion of "table-stream duality" as presented is likewise
| troublesome, as that is an implementation design choice, not part
| of the domain model. I don't think that it is a goal worthy of
| breaking your domain model, and suggest that it should not be
| considered at all in the domain model's design. Doing so is a
| form of premature optimization :)
|
| * That said, separating entity and event streams would keep
| table-stream duality but require more small tables, i.e. one
| domain event type per stream and another Booking stream to hold
| entity state as necessary. A Booking service can subscribe to
| SeatSelected et al events (presumably from the UI's back-end
| service) and maintain a separate table for booking-object
| versions/state. A SeatReserved event can be emitted by the
| Booking service, and no one has to know about the BookingUpdated
| event but replication hosts.
|
| Thanks again for writing and posting this, it really made me
| think. Good luck with your project!
| deterministic wrote:
| I design events following these rules:
|
| 1. You should be able to recreate the complete business state
| from the complete event sequence only. No reaching out to other
| servers/servies/DB's to get data.
|
| 2. The events should be as small as possible. So only carry the
| minimum data needed to implement rule #1
|
| That's it. It works really well in practice.
___________________________________________________________________
(page generated 2024-11-02 23:02 UTC)