I did enjoy reading the preview book for EventStorming, seems like a decent format for getting stakeholders involved in the design process and capturing business details. I'm pretty sure you can also do this with an org-mode doc and emailing the right questions to the right people, but discovery is hard and having a big post-it note brain storm with 30 people does sound like more fun!
I don't know, I feel like tech people are regularly amazed that other people can describe the events, processes, and data needs of their jobs if you just ask them. Likewise I grant that many people are poorly onboarded and end up "faking it to make it" in their careers. I've seen similar "people lighting up as they finally understand what the company does" moments through the use of literally any form of high-level modeling of the business. People are starved for context at most jobs.
I tried the EventStorming a few years ago with a dept for working out a new system , it was really eye opening how everyone referred to things - seeing it all laid out made them realise they all did things slightly differently, and referred to things differently. It was fun and very useful to not just me, but them as well
I don't think I can upvote this hard enough. Getting people to think and talk about what they do in context provides a lot of great insight in how to use tech (or not use it) to solve their problems.
Well that's rote learning for you, but I don't think it's the same issue as people avoiding knowing about context. I've met very smart people who know their silos quite well and refuse to acknowledge any picture bigger then they need to, strictly speaking.
The good parts of SOA, the event and CQRS stuff, data administration (and curation recently), and BDD all reduce to the same set of ideas.
But then there are always people that look at those and keep exactly the bad parts. Like the links from the article, that all but define event-driven architectures as software that use Kafka.
You're setting up an extra layer of difficulty around the system. Sure, the English is 'easy' to read, as long you do the hard work of converting the test cases to English. Then when something goes wrong or you want to know what's really going on, you have to translate from English back into the system's language to think about it.
The utility of Cucumber, it seems to me, is that it makes it more likely that the business level description of the test cases is maintained in sync with the technical implementation, which (if the test cases aren’t squirreled away in the tech team away from the eyes of people responsible directly to customers) makes it less likely that you experience drift between what you are testing and what users rely on the system to do.
Its not that it makes testing easier, its that, in the right social context, it makes testing more likely to be aligned with business intent.
A) Business will never read your tests. Whatever you are smoking that has you thinking they will, stop it. A.5) In the event they do, they will never extract the nuance of the glue.
B) that extra layer of translation, when combined with a multitude of different people who refer to or model different things in different ways is going to devolve into the Tower of Babel, and you'll soon find yourself in step level meta-hell trying to bridge in new cross cutting abstractions into huge swathes of code that will break for reasons that seem entirely irrelevant to the hackneyed step grammar.
C) Seperation of Concerns: Writing English/Natural language to be understood utilizes a fundamentally different approach to Authorship than does code authoring. Write code that works, then. Explain it with comments. Do not use DSL's because then you're just adding writing a parser layer on top of writing a test framework that works.
D)If I see one more spelling mistake I'm going to....
BDD is doable. As a testing professional though, I always emphasize the parts wherein the work is packaged and delivered in entire flows rather than on using a tool like Cucumber and making my testers suffer through writing English for people who will never look at code, even if you threatened them with bodily harm if they didn't.
Do what works for you though. I find it works okayish for integration or system level testing... But it's way to heavy for anything else.
I pretty much agree with you. Do you think there would be any value in having some sort of integration that goes the other way - scan existing test suite code and converts to natural language for surfacing to the business?
Devs are already doing this with adding comments with Github copilot .. might be an interesting way to close out the loop without putting too much process in.
That ones tricky, because most of computing is composing primitives distilled out of the ambiguous mess of a business context. Your implementations are the bones of process left when you've peeled back the skin, cut out all tha musculature, and removed anything resembling a squishy bit, just leaving a skeleton.
So you're asking the computer to look at bones, and vomit forth a description of just WTF you were trying to do in the first place, when the same set of primitives, configured in the same way, could solve problems in thousands of different contexts.
There may be more success going the other way: distilling the config of primitives from the messiness of requirements; which strikes me as something similar to what is called a prompt engineer; however, the biggest difference there is the additional burden of cutting through BS.
yeah, in the case of the second approach it might have to be more of a dialog than a one of translation from requirements, in the same way you might have a dialog with chatGPT to clear up ambiguity.
Not only that, but it enables product owners to actually express the features they want, in such a way as they can be implemented. Without that devs and product owners tend to second-guess each other.
I've recently introduced Cucumber and it's been an incredibly useful framework for collaborating between developers and product owners. I'd recommend "Writing Great Specifications" by Kamil Nicieja.
I own several apps that I wouldn't be able to maintain by myself without good end to end tests. The nice thing about cucumber is I can come back to a test a year later and instantly understand what it's testing for at a requirements level vs. having to reason through the gnarly details of what it actually does, then work backwards to the original requirement.
It shines the most in gnarly login/sign up flows that always evolve into all kinds of weird edge cases over time.
> The nice thing about cucumber is I can come back to a test a year later and instantly understand what it’s testing for at a requirements level vs. having to reason through the gnarly details of what it actually does, then work backwards to the original requirement.
Yeah, its good to point out that the people who need to think in Business Requirements rather than implementation detail terms are, often the same people, at different moments, especially on smaller teams (or teams that do something closer to Manifesto-and-principles-Agile, rather than bureaucratic-cargo-cult-Agile.)
I disagree. EDA is about how microservices are integrated and, if you establish it, how consistency between them is maintained. Note the talk of ordering guarantees and the like. See also event sourcing and the shifting of responsibility for maintaining data stores: historically data stores were maintained by an authoritative microservice while in many EDA systems multiple copies of the same data stores exist across the system.
+1 although I wouldn't call them buzzwords. The whole architecture community tends to use vague metaphors, borrowed or overloaded terms (events, commands, nouns, verbs and the like) without ever defining them technically or formally. Due to some kind of Illusion of Transparency [1] architecture people often overestimate the degree of common understanding about such terms.
This article is fairly basic, and may elicit a "well, duh!" response, but I see the issues mentioned in the article coming up over and over again with people new to event driven architectures.
I can think of two recent platforms I've looked at that implements very complex micro services and event messaging schemes that could (and should) all be replaced with simple in-process pipelines that can achieve the same outcomes. Every generation of developer seems to need to learn anew that in-process communications are orders of magnitude faster than (and more reliable) than inter-process communications, and that your chatty OOP design will not fair so well as a chatty distributed networking application.
Let's say I'm not using microservices and I'm not interested in using microservices, but the idea of event-driven architecture appeals to me. Let's also say that I'm using Python or C# or Ruby or Go. Should I be seeking out some kind of EDA framework? Or do I just need some kind of task queue/cache and a pool of workers looping over its contents processing events?
You should stop and first think about your big picture goals. What is the source and timescale of your events? What is the lifecycle of higher order processes you build using event cascades? What are your reliability, durability, and recovery concerns? Get to know your concerns before you start seriously evaluating any tools or design approach. But, before you go too far in this thought process, wander over to the "you cannot have exactly-once delivery" thread and think on that for a while too.
Distributed reliability or consistency goals may lead you away from event-driven and into state-driven approaches. You start to model the actual long-term process you want managed, and the producers and consumers can inspect or update a metadata record representing that process as they play their parts in it. I.e. you are better off with some idempotent order-placement protocol to create a customer order record than some simplistic "customer clicked order button" event which could leave to ambiguous scenarios where orders are placed, lost, or duplicated. Similarly, you don't want abnormalities in your fulfillment process to introduce more ambiguity as to whether an order has been shipped or not. This is where "business process" or "document process" frameworks would be more appropriate than event-driven ones. Such systems may have event-driven characteristics in them, but that's more of a scheduling optimization and not the fundamental state model.
On the other end of the spectrum, you can have sensor-based systems where measurement samples are streamed. You cannot have the monitored environment pause until a sample is registered. You can only observe or fail to observe the environment while it continued on its natural course. You really want some kind of capture, logging, and analysis platform. It would be inappropriate to treat the individual samples as events in an EDA, but you might synthesize events from an analysis, i.e. when a measurement value crosses some threshold or shows some time-dependent signature across multiple samples.
It doesn't have to be that complicated. Set up a list of callbacks that need to occur when a specific event is emitted, and call those callbacks.
You can also make it dynamic by having a "register_event_hook" function that puts a callback on the event hook list. Tune for performance as necessary.
I generally include these in the controller which emits the event, or as an ancestor class for those controllers.
And if your needs evolve to the point that it's simpler to use a framework later, you'll understand your needs better and you'll be able to pick one that will best work for you.
Elixir Commanded is one of the best and full featured libraries/frameworks I have ever seen for working with event driven architecture and cqrs. Add in something like Oban Pro and you have a robust system that handles failures and scales
I read this, and this article has a lot of words that I don’t know, and that make me feel intimidated for having missed out on something important for the last ten years of my professional career.
The first time someone told me about EDA was when I told them I had problems optimizing a certain SQL query, to which their answer was whether I have considered using event sourcing or storming.
> missed out on something important for the last ten years
* If you write down what happened ("events"), and don't throw away said events, then you can always compare the current state of the system to how you think it should be (according to the events).
> I had problems optimizing a certain SQL query
* If you don't mutate your events, you are allowed to share them. Perhaps the DB you were working with had an unsuitable structure, making it hard to write fast SQL. No worries! Create a second service/DB with a structure designed for fast querying. Slurp the events which you did not throw away into your new database, and enjoy fast queries (without needing to break or cause regressions in the first service/db).
> * If you write down what happened ("events"), and don't throw away said events, then you can always compare the current state of the system to how you think it should be (according to the events).
This, by the way, is the fundamental principle of accounting. When you hear “reconciliation” this is what they’re doing - comparing some data reflecting real events against a log of intended events. For example, a nonprofit might reconcile their bank account (actual $ amounts) against GL files generated from their CRM (expected $ amounts.)
Or the events do still make sense, it's just the processing rules for them have changed several times over the years and of course, the event "use the business rules ver. X.Y from now on" is not logged anywhere.
> Yet, the event producers shouldn’t assume getting a response.
This is why we can't have nice things. The number of bugs I've seen caused by this statement/core tenant of EDA makes me question people's sanity when they chase EDA.
> Events are facts; they can (and should) have multiple receivers. Yet, the event producers shouldn’t assume getting a response.
What this means is: There may be many consumers listening to payment events - a PaymentHistory service, a CustomerFraudDetection service, a Settlement service, a Bookkeeping service.
If I publish the fact that {Customer x paid $3.40}, that event has been persisted, and is considered to have happened. What's to be done if something goes wrong downstream? From my point of view, I can't do any more. It is up to the downstream services to restore themselves to a good state. If I 'retry' that message, that's exactly the same as if the customer had paid twice.
>> If I 'retry' that message, that's exactly the same as if the customer had paid twice.
Transactions (events) and messages are not the same thing. There should be no problem resending a GUID identified (or maybe a timestamped) transaction.
Respond meaning by sending a receipt back the to the original sender?
Before. Because then the sender can assume it never arrived if it never receives a response and can try again. If the receiver has already processed/recorded it, it goes through the motions again, but in an idempotent way that doesn't break anything by performing the action twice (or more). Eventually, the sender will get the response that it doesn't need to try again and it will reach resolution.
This is an extension of the two generals problem and can be totally resolved by using an idempotent design. However, you can never guarantee a resolution is reached on any given attempt, and that is by design.
My personal favorite approach is not using timestamps. The sender attaches a unique GUID, and the receiver records that GUID along with the processed event record. If the receiver sees it 100 times, it doesn't matter. It will do nothing the next 99 times, and tell the sender that it performed the task even without actually repeating it.
Eventually the sender will record that and stop asking.
You may need a few more words than that. My preferred definition of idempotency is that your actions take you to a desired state - if it didn't work, try again, and hopefully you'll get to the desired state this time.
The only time I've encountered an idempotency implementation in the wild, the team used the other interpretation of it, that is: the system will always respond the same for the same input. So if you try something and get an exception, they went through great lengths to ensure you would also get an exception the second time as well.
Your definition is generally inline with mine. The small exception is you can retry as many times as you can, once the desired state is reached, the result is the same after that no matter how many times you keep trying.
Using the example given, I (as producer) can retry the message however many times, the response I get should be the same. Implementation detail usually involves some kind of ID so downstream consumers know that they have process the said ID.
I do not believe if there is an exception, then future retry produce exception as well. But perhaps I misread your example.
The entire point of idempotency is not to always respond the same every single time, it's that the user can always expect the same response every single time. If the user receives a response it does not expect (or no response, etc.) than they don't want the same response on every retry, they still want the expected response
In a perfect world, an idempotent app responds the same every time. But in a perfect world you don't really need idempotency.
Oh, the word "event" strongly implies that you can filter, reroute, multiplex, store, branch, or do whatever with them. You can't do that if you require a response.
If you create an architecture where your requests need a response, you'd better not call those "events" and ignore all the event related literature. If you want events to work, you'd better architecture your software to work without responses.
We can't have nice things because in the real world most EDA designs are pretty naive and it's painful to look at them.
If your events are pure telemetry - samples of some data, some event that is standalone like a click, etc. - then life is easy. A missed event is no big deal, most of the time, and if it is you need some message-transactional, lossless event delivery mechanism, assuming you can pay the cost in performance or $, but those are both pretty easy concepts and people can mostly go from zero to understanding them in a day.
And here is where the trouble starts.
A lot of events in the real world are actually events that represent statefulness in the form of what are basically a log of state machine transitions, even if (because people don't really know how to build such systems) the modeling is not explicit.
In an ideal world with some publisher A and some end receiver C with perhaps some number of pipeline processors B, as long as the path from A to B to C is such that no drops or re-ordering can occur, things are should be fine. A sends to B+, the last stage of B sends to C. All good.
But in the real world, flakiness exists - bugs are _common_ and non-telemetry events are a problem:
* conveyance mechanisms are not actually reliable - they tend to have bugs, and this includes favorites like Kafka
* A, B and C can each have bugs - they might drop a message, they might _do the wrong thing_, they might persist some incorrect state, etc.
This is how you get into the situation I dealt with last month where the SRE team had to occasionally fix rows in the database where due to a bug that lasted for months some rows would be wrong, and sticky wrong because the system didn't have any kind of inconsistency-discovery mechanism even though the state data was always available.
Almost every event-driven system I've seen in the real world lacks the ability to detect this even when the source of truth is available (A, for example). Even a simple re-transmission or some digest of the state to detect inconsistency between the bottom and the top layers is routinely absent. It's pretty bad.
By analogy, anyone who has done low-level work will be familiar with the very different classes of events that are usually called "edge triggered (notified once, lost after)" or "level triggered (visible for as long as the state persists)"; more broadly there are stateful and non-stateful. Level triggered is _always_ easier to deal with because you can implement periodic self-check to recover in the event that an ephemeral bug has caused the event to be lost or mis-processed. There is incredible irony in the fact that big complex EDA systems have lost the thread on what is, in fact, the easy case.
While they cover how it should be done, I come across many EDA's neglect to account for operationally providing forecasting and predictability. Without gathering from the business what should be expected and implementing that (within messages, for example), we won't know when an event expected to arrive never arrived, nor when too many events arrived within a given time period. This is mainly a function of developers implementing only for the successful execution path and not for failure modes.
Mature code bases that haul in the bags of cash for a business in my experience tend to have more code handling the failure modes and edge cases than the path handling the successful execution. It can be a 90-10 split in cases where the business function is considered fully-automated (though of course that is actually an asymptotic, Platonic ideal). IMHO, too little is taught about coding for graceful failure recovery; it should be in the logical journey from TDD all the way through into devops. More often, what happens is this property is ad hoc accreted over time in code bases useful enough to stick around in production for a long enough time.
I concur with this article. The main pitfall I've seen with EDA (or really any other design paradigm) is buzzwords.
Buzzwords are pernicious because they induce a socially held belief instead of an intuitively grasped concept. This tends to lead to whatchamacallit architecture (aka distributed monolith casserole). To counteract this there needs to be a grokking process by the organization to dissolve the buzzwords into clear organizational understanding and buy-in. This exercise is often overlooked to ill effect.
Considering that object oriented programming began as a message passing and ended up as a request response paradigm (and the entangled mess it can be), I suppose event-driven will follow the same path.
I’m not very literate in CS; I don’t understand your characterisation of late OO as a request response paradigm. Isn’t calling a function or method to get a result just procedural programming 101?
Yes, it is, and OOP was all about message passing, not synchronous calls. The current incantation in pretty much all OOP languages (excepted maybe Smalltalk) is basically procedural programming with namespaces.
"All about message passing" was Alan Kay's vision of OOP, which was very distinct from what is considered OOP both before and after. He did coin the term "OOP" himself, though, so of course his definition carries a lot of weight, of course.
Even Simula, the original OOP language, is much closer to the procedural-ish OOP than to Smalltalk.
Erlang, Objective-C, Ruby, JavaScript, CLOS still offer that as concept.
Then all the languages that support Component Programming via traits, interfaces, data classes, OS ABI like COM, or whatever they feel like calling it, do as well.
Genuine ignorant question: is this a language problem or a coding problem? I'm thinking of "sending a message" as a synchronous function call. Could you not code in a style that passes messages around?
The true goal of event-driven is the separation of concenrns, so you don't call `execute_payment`, `send_confirmation_email`, `include_in_report_dashboard` etc inside a `complete_order`.
Instead, you emit "order_issued" event and all subscribers can act based on the event, completely unknown from `complete_order`.
Whether something is already implemented in a language's core library, or a package you can use, or you build your own library, is immaterial. You can implement any patter with any turing complete language, and you can build your own language.
Correct me if I'm wrong, but you asked if message passing rests on language facilities or code style. Answer is neither. You can implement message passing patterns in Forth, in C, in Python, or you can use Erlang or Smalltalk and get it out of the box.
I wasn't making it exclusive. I think the answer is either, not neither.
The thing I wasn't asking was "can I implement a language that can do this in Java?" which is why I don't think the Turing completeness comment is particularly relevant.
At times, I still fall for buzzwords in the technology space.
Looks shiny and most of the very vocal devs are harping about it - maybe worth a try.
At the end of the day, in the absence of buzzwords and hype, I sit back and come to the conclusion that tried and tested technology will still be able to do the work 99.9999% of the time.
I hold the .00001% for those possibilities where you are doing something breakthrough that in itself merits to be associated as a buzzword.
Until then, always proceed with caution and rationalize whether it is only hype or it is something worth adding to your arsenal.
After seeing GUIs done in Visual Basic 3-6, I decided that event driven anything is a terrible idea.
Even a simple thing like keyboard controlled video games best use a keydown or keyup event to simply set/clear a key state, and everything that cares should monitor (poll) the key state. If your only input is events, convert them to state ASAP because YUK to event driven software. P.S. see Therac-25
> I think the point is you're not doing anything with the event immediately, just setting the key state so it can be polled later.
Which is event-driven architecture. A component signals that something happened and other components take in that information and update their local state as appropriate.
No, it's not event driven. Maintaining key state up or down and treating it like a discrete I/O input in the rest of the code is not event driven. The event doesn't trigger any behavior. It's an example of how to handle events if that's all you've got as input - map it to non event driven design.
Yes, that's right. We're talking about EDA, not CQRS or the like. EDA events signal a change of state from one component for other components to synchronize on. They are not commands to react to.
Except when using a USB keyboard and can type faster than the keyboard can determine the keys pressed especially when when using modifier keys like Alt and Shift and the USB bus is being overwhelmed with other USB data.
I don’t see a mention to this, but in event-driven architecture it’s really really important to either strongly guarantee order (affecting availability) or you need to design it around commutative operations / convergent state machines (better availability, but harder to design). Without either of those, it’s asynchronous CRUD and it’s much harder to guarantee correctness under concurrency.
I agree some kind of event-driven architecture is ideal for typical business & admin CRUD. However, our existing languages and toolsets seem a poor fit and I've yet to see a good one. They force things into hierarchies and make it hard to search and manage events outside the official hierarchy. Relational is more powerful than trees, so we should find a way to leverage that.
I'd like to see languages & IDE's that assist some form of "Table Oriented Programming" (TOP) were either the event snippets are stored in an RDBMS, or at least the RDBMS is used by the IDE to search and manage the myriad events. Then can search and view by SQL and SQL-bases query tools. Developers wouldn't be forced to think in trees.
Here's what a typical generic event handler table may resemble:
Event (table)
----------------
ID
Area // general app category
Entity
EventType // Search, Listing, Add, Edit, Delete, Process, other
Stage // Ex: initialize, loadTemplate, adjustTemplate, preQuery,
postQuery, submit, queryErr, failedValidation, passedValidation,
preDisplay, postDisplay
Tags
Version
SourceCode // Actual code or a pointer/link to it.
In most cases the defaults or other attributes would be good enough such that explicit coding isn't needed, and thus most coding would be for specialized things.
// Sample event function/method pseudo-code
Public void AAA_EEE_TTT_SSS(EventState ev) {
...
if(problemX) { ev.statusOk=false; return; }
...
ev.statusOk = true; // final status of event
}
Where AAA is the area name, EEE the entity name, TTT the event type name, and SSS the stage name. These four columns would be indexed for quick searching and grouping. Under the hood it may still use the language's preferred file trees for generating or saving snippets, but developers wouldn't normally have to know or care about that tree.
I have been experimenting with TOP, but the R&D job is too big for me alone. I see and smell the potential power to automate most CRUD grunt work using TOP & events, but gluing it all together in a nice way is a work in progress. (I don't claim it's ideal for high-volume apps, but rather for those with intricate business logic and a medium load.)
Tool and design pattern marketing is one of the weirdest traits of software development in my opinion. I don't know if other fields suffer these too but it seems to me we waste so much time and effort because of tooling and design decisions made almost out of taste instead of plain old problem solving.
I did enjoy reading the preview book for EventStorming, seems like a decent format for getting stakeholders involved in the design process and capturing business details. I'm pretty sure you can also do this with an org-mode doc and emailing the right questions to the right people, but discovery is hard and having a big post-it note brain storm with 30 people does sound like more fun!
I don't know, I feel like tech people are regularly amazed that other people can describe the events, processes, and data needs of their jobs if you just ask them. Likewise I grant that many people are poorly onboarded and end up "faking it to make it" in their careers. I've seen similar "people lighting up as they finally understand what the company does" moments through the use of literally any form of high-level modeling of the business. People are starved for context at most jobs.