Microservices: The Key Errors You Can’t Afford to Make (Examples in Symfony)

Mounir Mouih
Level Up Coding
Published in
5 min readApr 9, 2024

--

Photo by Jp Valery

Designing software is no easy feat, especially when it encompasses numerous domains, responsibilities, and complexities. The microservices architecture style can resolve these challenges when implemented correctly.

Microservices require a lot of investments. Making mistakes in early stages of implementing this architecture style can be costly.

In this article, I will outline some crucial mistakes to avoid when adopting this architectural approach.

1- Adopting microservices too soon

Microservices architecture is costly in terms of infrastructure or human resources. Therefore, it would be wise to be certain that you need this architecture style at current stage of your project before investing in it.

I would suggest that every project has to start as monolith, and it should be the standard if you start a new project. In early stage of a project Monoliths are easier to bootstrap for small team, you should ensure that it is worth the investment, and the business is profitable before thinking of scaling it.

Before opting for a microservice architecture, you may need to consider adopting a delicate/disciplined architecture style, such as Domain driven design. This will be enough as first step toward a microservice architecture style, as DDD make it easier to extract microservices from your monolith, as the domain has been already segregated in your code base.

2- Mind the Sneaky Coupling

Coupling, is a software killer, and it can be even worse if the coupling is hidden behind a microservice architecture.
One of the main promises that microservices offer is loose coupling and independent deployability.

How this happens, The most recurrent example I’ve seen in my career is coupling by data, and I find it the most dangerous.

Consider the following example: a microservice tasked with managing invoices. The invoice is represented by an Entity — Invoice. Using an API engine or object serializer, we may expose the entity through our API. This is a standard example that I’ve encountered countless times. It seems logical, doesn’t it? A REST API is about resources. We may just need to expose our resource. Nothing wrong with that, right?

Well, it’s actually quite dangerous. What we’ve done here is inadvertently leaked the internal structure of our resource. I call it a contract leak, or coupling by data. Your client will be able to access and use your entity’s internal structure. That means that any change to your entity is a change to your API contract. Your clients will need to update their code as well. Congratulations, you’ve shot yourself in the foot! You can no longer freely refactor your application easily, and you will have to do a lot of gymnastics to keep your API intact.

Keep in mind that the issue can be deeper. if multiple microservices are in play, the contract leak will impact all of them.

Contract leak or coupling by data

Let’s consider the following example in a Symfony application:

We’re employing the serializer component to unveil our Entity. Once our Entity is revealed, it no longer remains exclusive to our microservice! We forfeit control over its contract. It now pertains to the client (whether another microservice or an actual client utilizing our API).

The solution

The preceding example illustrates an Implicit Contract. While clients receive a contract, it remains implicit; in reality, there’s no explicit contract in place. The solution is evidently to implement one.

An Explicit contract safeguards the internal structure of our entity, enabling us to refactor it effortlessly. Our microservice has complete control over our Entity.

Let’s implement it using Symfony:

Now the response/Contract is represented by InvoiceContract. The EntityMapper will handle the mapping between our Entity and it’s contract.
This way our Entity is completely decoupled from our Api response.

Explicit contract

3- Do not forget input Contracts

Contracts play a pivotal role in defining API responses and inputs. Having a well-defined contract for inputs not only enhances the clarity of our API but also facilitates data validation and clarifies intent. Designing Api’s this way make it easier to focus on Business logic since concerned are not mixed.

Symfony empowers Input contract’s using #[MapRequestPayload], which facilitates the mapping of our requests to a Data Transfer Object representing our contract.

I’ve made an article about this topic, I encourage you to read it for more details: How to properly handle Request with Symfony 6.3+.

4- Ignoring Log Aggregation

In a microservices architecture, debugging becomes more complex. It’s crucial to have a clear understanding of what occurs during a request. Each microservice generates its own logs, making it challenging to track which logs pertain to the same request across multiple participating microservices. Debugging can become a time-consuming process, resulting in significant losses in terms of money, time, and productivity.

Utilizing a specialized logging aggregation tool can streamline the debugging process and offer a comprehensive overview of the entire lifecycle of a bug, providing a valuable map for understanding and resolving issues efficiently.

Wrapping up

  • Before embracing microservices, assess whether they are truly necessary. Prioritize understanding your system’s needs and consider architectural styles like Domain-Driven Design (DDD) before transitioning to microservices.
  • Establish explicit API contracts for both inputs and outputs to mitigate hidden data coupling and safeguard the internal structure of your microservices.
  • Implement log aggregation to enhance debugging capabilities and increase productivity.

Thanks for reading, your clapping 👏, sharing 🔗 and subscription would be greatly appreciated if you find the content interesting.
I’m keenly interested in receiving comments, ideas, and engaging in discussions in the comment sections.

Follow me on X and github.

--

--