Robust and Developer friendly Application Architecture in PHP
Let’s explore architecture in which scalability and resiliency are side effect of our daily development, not something we struggle for.
In this article we will dive into how to build resilient and scalable architecture with full focus on development experience and delivery speed. We will dive into making such architecture a default state for our System, so scalability and resiliency is not something we need to struggle for, it’s just side effect of our daily development.
Yet before we will dive into “how”, we first need to explore what scalability and resiliency actually means.
Scalability and Resiliency traits
Scalability means that architecture will not break when the load increases. It also means that even on high peaks, we are able to distribute load and adjust.
On other hand Resiliency, means that System is prepared for failures. It will try to self-heal by default, and would require intervention from Developer only, if unrecoverable failure happens. It also means no data is lost in case of fatal errors, to make recoverability actually possible.
This both characteristics are also joined in processing isolation.
This means instead of requiring to handle multiple things at once (batch like), we allow for processing of each separately and concurrently. This way we can scale the processing, and in case of failure allow to retry — only the thing that actually failed.
Scalability and resiliency are actually characteristics of Message based communication. So by exploring Messaging we can actually get to know how to build scalable and resilient Systems.
However introducing Message based communication due to it’s complexity can often lead to frustration, and if not implemented correctly can even lead to demotivation.
Demotivating Architecture
So if Message based Communication solves resiliency and scalability, then after this architecture is built, in theory we should be good.
Yet this more often than not, is a false impression. Messaging and resiliency patterns brings a lot additional complexity, and if we won’t introduce higher level abstractions, developers will end up in investing time into setting up configuration, writing boilerplate code to fulfil required steps, and wiring things together in their daily development. The time to actually do business feature or task is actually decreased because of that.
This often leads to questions, whatever we do have time for focusing on resiliency or scalability, as we could deliver given feature much quicker by avoiding this architecture completely. Enforcing such architecture may lead to even worse results, by creating a feeling of being forced to use something which is not helpful. This eventually may lead to demotivation and lack of enjoyment in the project.
To solve complexity and additional development effort that Message based communication brings, we need to push abstractions higher. To the level where it becomes easier to write code which is resilient and scalable, than code that does not follow those principles.
The architecture must create environment where Developer can focus on their daily task and business flows, not on setting up configuration, writing repetitive steps, and wiring. And that kind of architecture, which not only solves scalability and resiliency problems, but yet creates smooth development experience I do name — Business Oriented Architecture.
Business Oriented Architecture
Business Oriented Architecture makes it easy for Developers to fully focus on business features / tasks they are working on, yet benefit from all scalability and resiliency patterns working under the hood.
To fully understand what does that mean, we need to explore three pillars that this architecture introduce.
Business Oriented Architecture introduces three pillars to make the feature delivery as smooth as possible. Each pillar abstracts given set of problems so we can focus more on things like business logic and flows.
Let’s explore what does each pillar mean, and how it was implemented in Ecotone Framework:
Resilient Messaging
So at the core of Business Oriented Architecture is Messaging, based on Enterprise Integration Patterns.
This abstraction level, ensures that every component / module or application is easily connected together, yet fully decoupled.
At this level everything is a Message, which flows over Message Channels. This means we can easily switch any part of the code to run asynchronously to ensure scalability.
This level of abstraction also covers Resiliency patterns like:
- Automatic Retries and Dead Letter
- Idempotency
- Outbox pattern
When Ecotone Framework wasn’t open sourced, Resilient Messaging abstraction was the only one available (other pillars have not existed yet).
This however required from Developers to configure different parts of the Framework, which contributed to several drawbacks:
- Time was invested in setting up Messaging configuration, which more often than not was pretty much similar between features
- As things were not auto-configured, it exposed possibility for setting up configuring incorrectly, which lead to debugging and reverse engineering
- It also coupled Framework with Application level code. When configuration in Framework have changed, it required changes in related Applications
So above situation was far from ideal and required different way of solving things, and for this Declarative Configuration was introduced.
Declarative Configuration
Aim to make daily development quicker, more robust and to decouple Application level code from Framework level code, created the need for introducing second pillar — Declarative Configuration.
The goal was to make Developers focus on the business features they deliver, yet to keep all scalability and resiliency as the foundation.
At the end Developer was meant to simply install the Framework and get all the goods without any additional configuration. Basically even Developers without the knowledge of Messaging or Resiliency patterns were meant to use Ecotone without any problems, yet with all it’s benefits. For this Declarative Configuration using Attributes was introduced.
- Instead of providing configuration on how to wire given class and connect it to Messaging, we simply mark given method with CommandHandler attribute. This is enough for Framework to know what is our intention, and do the configuration for us:
- When we will add Asynchronous to the Command/Event Handler, this specific Handler will be executed asynchronously. This is also related to processing isolation mentioned earlier, it’s not the Message that is handled Asynchronously, it’s Message Handler that is. This way even if multiple Event Handler subscribers to the same Message, each of them is processed independently.
- To delay given execution, it’s enough to add Delayed attribute. The same way we can add for example priority or time to live.
- Declarative configuration also contributes to full decoupling from the Framework. Our Commands or Events classes that we’ve created does not extend or implement any Framework specific classes.
- The same principle of decoupling applies to Message Headers, they are not part of our Application level classes. They are part of Ecotone’s Message which is abstracted away, yet always available for us.
What is worth to mention, is that Headers are automatically propagated, so we don’t need to pass them around in our Command or Event classes.
- The decoupling process can also done in our Controllers. If we want, we can simply use routing and let Ecotone do the transformation from given Media Type to Command Class on fly
- Based on routing, we can also leverage our Messaging abstraction that allows for input-output based architecture. Which creates environment in which workflows can be build
There are of course much more Attributes and features available, but the above is enough to show how daily development became smooth and decoupled from extending or implementing any Framework specific classes. And when given Message Handler is not needed, we simply delete this method together with attributes, and given functionality is cleared.
Thanks to decoupling from the Framework, any internal changes to the framework do not affect Application level code anymore. This basically allowed Ecotone to keep same major version for over three years now, without a single breaking change while delivering a lot of new features and even huge refactors. From the side of Developers using the Framework they do not need about framework changes anymore, as they simply upgrade the package and everything continues to work.
Yet there is one more things that can help us with daily development, which speeds up development even more — Building Blocks.
Building Blocks
We could of course use Ecotone based only on two first pillars, yet by adding third one - we basically create environment in which we focus mainly on the business logic — therefore we start to deliver features really quickly.
One of the first patterns which emerged to streamline development even more are — Aggregates. To understand the need for them, let’s look how often Command Handlers does look like:
This code is repetitive by nature, and is called orchestration level code. Writing repetitive code is not best way of using our time. It’s not only our time that we actually invest into this, it’s whole team’s time, as now as it was written someone need to review it, and as it’s merged it becomes internal part of the project, which need to be maintained and changed together with the feature.
So one of the Building Blocks that Ecotone introduce, are Aggregates.
Aggregates allows us to keep the business logic within the Entity / Model, and Ecotone will ensure to do all repetitive steps — like loading, executing method and storing. This way all the orchestration code is gone.
Ecotone also provides advanced support for Event Sourcing with different persistence strategies. This support can be used directly by storing events in Event Store. However it can also be used with Building Blocks like Event Sourcing Aggregates.
If we use Event Sourced Aggregates, we basically avoid writing orchestration code, code responsible for persistency or code responsible for publishing those Events. This creates a space for Developers to fully focus on the business problem at hand.
This joins nicely with next build block — Projections. Projections can be triggered synchronous or asynchronously, and will project given view based on the related Event Stream:
As you can see the speed of delivery basically sky-rockets while using Building Blocks. Instead of writing a lot of orchestration code ourselves, we simply state what we want to achieve with Declarative Configuration, and write only business specific logic.
The speed of delivery is not only directly related to the code we write, but to what happens after — so when we use Building Blocks there is much less code to review, it’s easier then to spot potential bugs, and less chance to configure orchestration incorrectly.
Summary
Business Oriented Architecture creates environment in which Developers can fully focus on the task at hand, writing business related code, yet keep the System scalable and resilient by default.
There is no need to force anybody into doing that, because Message based communication becomes part of design, that does not require additional time to invest.
Ecotone Framework is built in PHP, so if that’s language of your use, you can explore more details here. And if you have not used PHP recently, I encourage you to explore it, as with Ecotone it became really easy to build even the most complex systems with ease.