Backend architecture

Designing micro/mini service architecture is a discipline in its own right. How do you split services to make them independently scalable? Which service communicates with which? What is statelessness and which components are suitable for it? What about the technical aspects of components, such as which languages to write them in? And if you're considering transitioning from a monolith to microservices, what should be considered for the transition?

Monolith vs. Microservices

Like everything, it is important to first consider the potential benefits of changes and not just follow current trends. Even large projects like Windows were initially written as a single codebase, then moved to many small codes, and finally found a hybrid optimum for the size of separate modules.

The greatest advantage of a monolith is its speed—calling a method in another part of the code costs almost nothing. Microservices deal with latency, although it can be significantly reduced by choosing gRPC over REST. On the other hand, separate services excel in that various components can be written in different languages, scaled, or replaced independently, thus rejuvenating the entire system piece by piece every few years.

Communication Between Services

Services can communicate in several ways. The most common is direct calling via REST or gRPC. If speed is not crucial for a given service, for example, it handles asynchronous postprocessing with small input, REST is preferred for its simplicity of use. These are simple calls over the HTTP protocol that can easily be mimicked by tools like Postman or even a web browser. For transferring larger volumes of data, from kilobytes upwards called synchronously, it is worth considering the gRPC protocol built on significantly faster HTTP/2. This protocol is based on the idea of calling remote services using their methods, just as if they were in the same monolithic block.

Communications through queues are also possible, suitable for batch processing of objects, or various events where service A leaves data for service B on persistent storage, then trigger service B for run. Cloud solutions have their services for processing queues; for example, in the case of Google, it is Pub/Sub. The specific type of communication ultimately depends on the particular services.

Service Language

The choice of code is often a hot topic among programmers, sometimes with significant emotional undertones. Rationality is important here. When choosing, it is necessary to consider the speed/ease of development, the availability of programmers on the job market, and the suitability for the given service. Video encoding in Python is not a good decision, just as managing an employee directory in C. However, if we swap these two languages, their use cases make more sense.

If the architecture uses five or more programming languages, it will be challenging to supply new programmers. Even if it made sense at some point to write one of the components in Haskell because we didn't want errors in a critical service, it proved to be an unfortunate decision. Adding another person to such a component can be an impossible task.

Here are some common backend choices:

  • Python: The number one choice in machine learning applications or web development. It is very simple and often humorously referred to as just logical English. However, it has poor package management, which can lead to daily conflicts over time. It does not support running on multiple processors simultaneously, making it difficult to detect saturation and trigger scaling in the Cloud. And it runs very slowly. If it's not about mathematics, where it communicates directly with the C library, and is very fast in that case.
  • C/C++: Known for its high execution speed. Loading an ML model into it can be nearly superhuman. Moreover, there are few programmers capable of writing something in this language in a reasonable time today. It is suitable as a non-mathematical model where execution speed is critical, such as a library for Python or Go.
  • Go: Manages high execution speed and development speed, and it is simple to write multiprocessor programs in it. The language, developed by Google, is a good hybrid between Python and C. The downside is still the significant lack of support libraries, especially mathematical ones in the machine learning world. Ideal for applications requiring high throughput or various workers.
  • Rust: A popular language recently that, like Go, comes with its solution to the high complexity of working with memory in C, while trying to maintain its high execution speed. Unfortunately, it is quite complex, still demands high requirements on the programmer, which prolongs development time, and still lacks support libraries for ML.
  • PHP: Once a popular language for the web, now being replaced by Python and JavaScript. It no longer belongs in new projects.

Statelessness

A common mistake is giving every service access to the database or disk because "it might come in handy." For some services, however, it is better if they are only "flow-through." If we have a limit of 200 database connections and each of the 20 services starts scaling during high traffic, they will quickly run out. This way, they can overwhelm any of our media.

So-called stateless services, which do not read or write anything from anywhere and only exist "in the ether" to transform input to output, scale very well. They can do preprocessing or postprocessing for us in a relatively high number of copies of themselves.

Conclusion

Choosing the right architecture depends on many factors. If you want to consult whether yours is efficient or are planning a transition from a monolith to a microservice architecture, we can meet over coffee.