Lessons from “Good enough” architecture

Definition of Architecture

According to ISO : “Architecture is fundamental concepts or properties of a system in its environment embodied in its elements, relationships and in the principles of its design and evolution.” . Well, smell of academy, I don’t like it .

The next explanation is make more senses : Architecture represents the significant design decisions that shape a system, where significant is measured by cost of change (Grady Booch).

And one important fact is, this is not the work of someone labeled themself “architecture” only. Architecture is not an upfront activity performed by somebody in charge of telling everyone else what to do.

Back to the academy definition, Architecture is a property of a system, not a description of its intended design. A system always has its architecture, intentionally or accidentally :))

So, What is Good Architecture

This is kinda an arrogance question. It’s likely to tell what is a good car or a good wife :>>. Mature man will say : “it depends” 😀

Any kind of Architecture must satisfy all Quality Properties. Academically, (ISO things), Quality Properties of a system/application are :

  • Functional Suitability –> Does your functions do what the customer want, many bugs or not ? :)))
  • Performance Efficiency –> How fast does your system response to users?
  • Compatibility –> How easy is it to integrate with other system or application?
  • Usability –> How easy for users to use your system?
  • Reliability –> Does an unexpected error shut down entire system, or a part ?
  • Security –> Can it be hacked ?
  • Maintainability –> Is it easy to change or add logic ? Do new guys understand source code easily?
  • Portability –> How easy to setup your system ?

So much to take care of for a single decision at a moment !! . But it depends on what kind of your application, what is the purpose that you can temporarily more care about one property than others or even sacrifice one or more properties to optimize others. Deadline & Budget are un-official properties according to ISO but no one can deny its significant effects on ensuring above properties :))).

It is all about Balancing

For example in Scaling system challenge : Everyone want their system “easy to scale”. Normally “Scale” term makes us think about a system like Facebook or Google with a half of planet users, but actually there are 3 dimensions when talk about scaling :

  • Logic Scaling: How complex is the business logic ? Is it written in a day or frequently changed depends on unknown conditions ?
  • Load Scaling: How many users ? Your system is going to serve a dozen of users or a haft of the Earth ?
  • Team Scaling: How many developers are working on it?

Logic Scaling often appears in B2B multi-tenancy CRM/ERP applications with highly customizability. As instinct, Architecture guys likely to decide to generalize everything by introducing configurations, settings, modules with on/off ability to satisfy all anticipated client needs and you may think that writing if/else for a certain clients is “hard code“.
The needs can be so general like button color, website logo, email template, etc, to very specific per client such as configuring workflow, assembling reports, employee task assigning logic, etc .. The more specific need, the more sophisticated configurations is, and the more complexity in source code. It is reducing Maintainability to enhance Functional Suitability.
It works for most cases but a fact is that this kind of applications often have a small group of big clients that pay well for features and customizations, and the rest just uses what are available. Soon or late, those big clients will request you to make some unique logics that totally different from others. Those kind of super specific requirements obviously adds super extra complexity to the source code. As a quick judgment, those extra complexity for a small set of users is NOT worthy, right? . But it worths as business perspective. So the beginning idea of generalizing everything is not perfect. You now wish for a method to put all specific logics for certain clients, to manage them easily, and the if/else code for certain clients turns out not a “hard code” anymore if it is put in the right place. It is the balancing between generalization versus specialization.

Load Scaling is the decision between Horizontal versus Vertical. As we know that there are 2 common ways to increase the load that a system endures :

  • Vertical method : add more RAM
  • Horizontal method : add more machines and apply load balancing techniques

Adding more RAM is the easiest way but the limitation is the maximum amount of RAM. Adding more machines solves vertical method limitation but it adds complexity to system. It is reducing Portability to enhance Performance Efficiency : more deployment effort than before.
But how about Horizontal method, does it have any drawback comparing to Vertical one? Yes, you mostly have to write more logics to synchronize session data when user connects to more than 1 instance, or logic to select the best instance to help users persistent connected with 1 instance. It may lead to more problems when the you are scaling the database, it adds more headaches when you want to index a few tables that located across multiple machines, or to handle relational operations in SQL type database , etc … It is reducing Maintainability to keep Performance Efficiency too.

Team Scaling often appears in large company with multiple teams and hundreds developers. Team Scaling is more about management problem than coding problem. Too much people need a strict process to co-operate to avoid trampling upon others, maximize team’s abilities and separate team’s responsibilities. The most popular architecture nowadays for Team Scaling is MicroServices where each team has their own tech stack if provide it can integrate with others. Each team can have its own system and they ensure Quality Properties themself, inside their boundary. So the entire system contains other small systems. This is reducing Portability to enhance Maintainability.

The most difficult decision when applying MicroService is to separate responsibilities between teams. Wrong separation leads to heavy or strict dependency between teams and it ruins every benefits offered by MicroServices, and even weaken Maintainability and Performance Efficiency comparing to traditional Monoliths Architecture.

Traditional Monoliths Architecture can deal with Team Scaling using Modulation. Each team handle a few modules, each module contains a or a few features. This way, every teams work on the same tech stack but different modules. The heavy or not dependency between modules is not a deal here because they are already compacted. This method keeps both Portability and Maintainability in contrast to MicroService but may reduce Performance Efficiency when the Load is high. This again backs to Load Scaling problem.

Another example is Banking systems. Their most important properties is Security and they willing to decrease their Performance Efficiency, Maintainability and Portability to make the transaction more secure, more idempotent by using transactional queries, install more filter, IDS/IPS, etc. We can’t judge them as a “Not Good” architecture just because our CMS system serves APIs faster.

Don’t bias to anything. Everything has its own pros and cons. Understand them and use them wisely.

There is no good or bad thing, there is only “good enough”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s