All problems in computer science can be solved by another level of indirection." – Butler Lampson

The great majority of the software architectures currently in use are variations of the layered architecture, and what really sets them apart is the implementation details. Some might find this statement controversial, but in my experience, most software applications rely on code organized in layers to manage complexity. Some of the layers may utilize message queues or microservices, but that doesn’t necessarily make the architecture event or microservices based.

In-n-Out

All non-trivial computer programs, regardless of their complexity, accept some input, do something with it, and produce an output.

O I u n t p p u u t t

Modules

Most programs have some internal structure in the form of modules. Modules can be implemented by using functions, objects, or a mixture of both. Ideally, each module should be responsible for a well defined task and nothing else. This principle is called Separation of Concerns.

O I u n t p p u u t t M M M

Layers

Once you reach a certain number of modules, it often makes sense to introduce a hierarchy, and put related modules in separate layers. This is known as Multi-tier Architecture. Notice the direction of arrows. Communication is normally unidirectional, and usually obeys the following rules:

  • Modules can call the other modules in the same layer.
  • They can also call the modules in the layer directly below.
  • The modules in lower layers cannot call the modules in upper layers at all. In fact, they don’t even know if an upper layer actually exists unless they receive a call from it.
O I u n t p p u u t t M M M M M M M M M

Since the modules in a layer cannot make any assumptions about the structure of upper layers, it’s possible to make changes to the individual layers without affecting others. You can even completely replace a layer with something else. For example, the calls from the upper layer can come from a web page or a mobile app, and the lower layer answering those calls doesn’t care where they come from as long as they are in a format it can understand.

If you don’t follow the general rules above, layers will eventually intertwine, and as a result, your code will likely turn into spaghetti or a big ball of mud. Yes, these are technical terms :)

Events

At Faradai, we collect millions of sensor readings from thousands of IoT devices every day, but we don’t process them the moment they arrive at our data collection layer. We store the readings in a message queue first, and process them at a rate we know our servers can comfortably handle. Otherwise, unexpected spikes in traffic could place a heavy burden on the servers.

You can think of message queues as an example of event-based architecture in which the so-called producers (in our case, IoT devices) store events (sensor readings) that are then consumed by the respective modules in our data processing layer.

p p p p p p p r r r r r r r o o o o o o o d d d d d d d u u u u u u u c c c c c c c e e e e e e e r r r r r r r 1 2 3 4 5 6 7 c c c o o o n n n s s s u u u m m m e e e r r r 1 2 3

We could have sent the sensor readings directly from our data collection layer to the data processing layer, and the early versions of our platform had actually worked that way, but the amount of data we processed increased significantly over time, so we put the message queue as an intermediate layer between the two.

Microservices

When you “carve out” some of your modules or even layers and turn them into independent programs that you can call from your main application or any other application for that matter, you get yourself microservices.

Unlike modules that rely on the environment provided by your application, microservices are standalone entities. The usual way to access them is over the network, so it’s easier to place them in separate servers. They can be developed separately by different teams in different programming languages or databases if necessary.

There’s a price for the flexibility offered by microservices though. Instead of a single application to worry about, you now have multiple dependencies that may go down or be slow to respond, which can negatively affect the responsiveness of your main application.

There are tools to “orchestrate” multiple microservices such as Kubernetes, but it’s a good idea to do a cost-benefit analysis before committing to microservices as the added complexity might not justify the potential benefits.

O I u n t p p u u t t M M M S S S

Plug-ins

Plug-ins offer a way to dynamically change the behavior of an application through components that act on the data provided by the application (aka host). For example, at Faradai, we generate alarms upon detecting abnormal energy consumption patterns based on a complex set of rules unique to each client. Since every alarm scenario can be unique, we provide a plug-in architecture called RulesEngine. Each alarm scenario can be thought of as a plug-in that is coded in an embedded scripting language, and can retrieve and process data from the host.

Notice the bidirectional nature of the arrows between the application and plug-ins. Plug-ins have access to various application modules, and supplement the capabilities of the application.

O I u n t p p u u t t M M M P P P

Other Architectures

There are software architectures other than the ones we’ve gone over so far, but as we approach software architecture from a code organization perspective, these are the major ones. Other architectures such as pipes and REST focus more on data flow and access respectively rather than code organization.

Layers, Events, Microservices, Plug-ins all Combined

There is a time and place for all architectures, and they can usually live together as a hybrid architecture. It’s probably not a good idea to exclusively commit to a single architecture as each architecture has its strengths and weaknesses, and we can get the most out of them by using each one in places where they excel.


Related: