Fork me on GitHub

Polylith got posted on HackerNews, and there’s a bit of a discussion going:

polylith 7
Prabu Rajan14:03:39

Is there some article or documentation comparing polylith architecture to a microservices architecture? In a microservices architecture, all writes / mutations are done via events (loose coupling) and all reads done through an API gateway (like graphql) that aggregates data from different services to serve end user facing APIs. There is strict isolation of components within services, meaning components in one service cannot call components in another service directly, they can do so only via their REST APIs (if at all they need to, but discouraged). In polylith I see that, all components are co-located under components and I dont understand how I can set clear boundaries (or group components) such that a component that ideally belongs to a project cannot call another from a different project? Moreover, polylith architecture doesn't talk about event driven style of communication between projects


@U01UY47RD5E In which situation would you want to limit one component from calling another? In Polylith, if two components deal with separate parts of a domain, then it’s possible that they won’t need to call each other. However, restricting that by making the components physically incapable of depending on each other seems like it might cause many issues. The Polylith philosophy is to allow any component to depend on any other, and not to create “walled-gardens” where there’s added friction for reusing existing code. This does require good design; namely, picking the right components, naming them well, and exposing clear functionality via their interfaces. However, even on large Polylith codebases, we haven’t noticed this causing spaghetti code. (In fact, we’ve noticed the opposite, that Polylith has given us beautifully structured code, even at scale). In regards to event-driven architecture, there shouldn’t be anything in Polylith that would restrict you from using that approach. One way to achieve it would be to have bases that publish and subscribe to the events, who delegate to a collection of components for processing each event.

Prabu Rajan15:03:13

@U055RDVAV Lets say I want to create a neighbourhood community like application where there are communities. Communities have users. Users can create interest groups within communities and discuss topics in the group's forum. Users can post items for sale or post ads in the community's market place. I would have the following services 1. Users and Communities 2. Groups and Forums 3. Market place 4. API Here, API could be a ReST / GraphQL API that is the only one exposed to the outside world (mobile/web frontends). Other services have their own ReST APIs and are internal (not exposed publicly) I would want to isolate data management and access for all these 3 services in a microservices architecture style. I would not want components of one, talking to the other directly to components of another Sure, there would be common reusable components. These are more at the level of technical / infrastructural reusable pieces of code. We generally put them inside common-components and import them as dependencies in all services I am trying to understand the parallels to this in the polylith world


Microservices are explicitly mentioned in the documentation, and monolith vs microservice is a distinct aspect from polylith. That is, with polylith both are possible. Nothing in the architecture prevents calling another component directly if it's in the same project. But then don't put them in the same project. The only part that should be is the parts that can do the RPC calls.


@U01UY47RD5E Thank you for giving an example - it makes your point clear, and it gives us a concrete case for comparing the two approaches. I think your example highlights a couple of the fundamental flaws with the microservices approach. 1. Users would belong to Communities, Groups, and Forums, but if we split the architecture into “walled-gardens” where code in the “Users and Communities” service can’t access code in the “Groups and Forums” service, then it’s not clear where the user code should live. What about code that we definitely want to share between the services? Things like logging, databases, or connecting to a 3rd-party system. With microservices we’re forced to extract and freeze that code into a library, which slows us down when we’re working with it. 2. We’re forced to cement our domain and deployment choices together. Let’s say that it’s the Groups that get the most traffic on this service, so we’d want to deploy that as a separate service, which means extracting it from the rest of the Forums functionality. But then we’re stuck again, because they both share code. In Polylith you avoid all these issues, because you’re never forced to decide between making a domain or a deployment architectural decision. The components can be deployed as one single artefact, if that works for your use case, or they can be split across multiple services. At no point does Polylith restrict any component being able to access any other component. At no point do you need to ask yourself “should we freeze this code and pull it out to a library”. It’s maximum modularity, and maximum flexibility the whole time. I hope that makes sense.

Prabu Rajan16:03:08

@U055RDVAV Sure, I do see the point of the polylith architecture. It is interesting, offers more flexibility and improves productivity. I do want to use it, but since I am new, I am skeptical and want to play the devil's advocate. To your concern about code in Users and Communities not able to access code in Groups and Forums, it would be the reverse. Groups and Forums would need to call code in Users and Communities. In such cases that Groups an Forums need Users / Communities data a lot, we would end up duplicating a subset of the data in Groups and Forums or the public facing API service calling both and then aggregating their data in a single response My concern is only about the possibility of introducing tight coupling between components. If the architecture is not followed in the correct spirit, developers could end up tightly coupling one component with another. For example, lets say only a community's admin is allowed to create an announcement type of post (published to all groups). If the developer ends up depending on the auth component from the groups component, then it becomes difficult to refactor the code later on. Ideally, I would have designed it like 1. auth is a component in Users and Communities service exposing an /auth API 2. When a user tries to use the :post /groups/posts API exposed by the API ( graphql API) service, it would call the /auth API with the logged in user context and based on the reply, would have called /groups/posts API exposed by the Groups and Forums service The following would be typical code organization in a microservices based mono repo my-workspace/ ├── common │ └── components │ ├── auth │ ├── cache │ ├── db │ ├── events │ ├── logging │ └── util ├── groups-forums │ ├── api │ ├── components │ │ ├── comments │ │ ├── forums │ │ ├── groups │ │ └── topics │ └── events ├── market-place │ ├── api │ ├── components │ │ ├── items │ │ ├── orders │ │ └── posts │ └── events ├── search │ ├── api │ ├── components │ └── events └── users-communities ├── api ├── components │ ├── auth │ ├── communities │ └── users └── events


What would be the practical benefit of an internal HTTP endpoint vs a function call to the public interface of the other component?

Prabu Rajan17:03:01

@U8ZQ1J1RR It is so that all the public facing APIs could be extracted to a separate microservice that can be scaled independently and that microservice could talk to other services via their internal endpoints. Typically, its quite obvious that the public facing API microservice would face heavy traffic and would need to be scaled independently and hence would make a strong case for a microservice


That's a completely different discussion from preventing deep coupling

Prabu Rajan18:03:55

It is relevant because by extracting all public facing APIs into a single microservice, we are able to localize all the tight coupling in a single microservice instead of introducing a web of dependencies (hub and spoke architecture)


I think the core idea to understand about coupling in Polylith, is that component interfaces don’t allow for “tight coupling”. A component’s implementation code can’t depend directly on another component’s implementation code (which is how I’d define “tight coupling”). They always depend on each other via their interfaces. Whether an interface is a HTTP endpoint (like in a microservice), or a “pass-through” function (like in component interfaces), the resulting encapsulation, complexity hiding, and decoupling is the same. That’s why it’s not a problem to have a completely flat hierarchy of components in Polylith, and why they don’t need to be protected from each other.

Prabu Rajan18:03:34

Sounds good. I am starting a new project and intend to follow the polylith architecture using clojure. Will let you know if I have more questions. Also, one more question.. lets say I have the need to implement one particular project (microservice) in a different programming language (due to better tooling and community support available on that language), is it possible that I write components that would end up in that project in that language and the rest in clojure?


Great news! Please do let us know how you get on (and if you have any more questions). Unfortunately, you can’t share components across languages. So if you do need to build a service using another language, then you’ll just have to interface with it via its public API.

Prabu Rajan19:03:54

Thanks! So the code of the service built with another language would reside in a separate git repo outside of the polylith mono repo? or can it be a project with its own base within the same repo?


You can keep the code for the other language in the same git repository if you like (or a separate repo), but it will live outside the Polylith workspace for the “main” language (Clojure in this case) in both cases, because you can’t mix languages within the same workspace.

gratitude-thank-you 1