How to get microservices right from the start
By Daniel Samson · 2025-05-16
If you really have an organisational issue, where your engineers are stepping on one and other, then micro-service architecture may be the solution.
This isn't a licence to reach for microservices by default — most teams are better off in a modular monolith. But if you genuinely have many teams colliding in one codebase, the trick is to get the structure right from day one. Retrofitting clean boundaries onto a sprawl of services is far harder than starting with clear layers, so here's the layout I'd reach for.
Architectural Layers

Every part of the system slots into one of a few layers, and each layer only ever depends inward. Core micro-services own a slice of the domain and its data, and never talk to the outside world directly. APIs sit in front of the core services, orchestrating them and exposing business functionality. Front-End applications run on the user's device and talk only to the APIs. Front-ends depend on APIs, APIs depend on core services — never the other way around.
Example Business Application

Take a typical booking application. You might have core services for Accounts, Inventory and Payments — each owning its own data and exposing a gRPC service. Above them, a Booking API orchestrates all three to take a reservation: it checks the account, holds the inventory, and takes the payment, without any of those core services needing to know the others exist. The web and mobile front-ends then talk only to the Booking API. Crucially, no core service reaches sideways into another's database — all coordination happens at the API layer.
Version Control Layout
First of each micro-service/api/app should get its own folder in side of a mono-repo. However, in order to make it cheap to create you should consider creating templates for the different layers of the application.
CLI / Build System
Typically you are going to have a scripts folder, that contains how build, test, and deploy the different parts of your application. You will use a CLI command-let at the application root: Python, NodeJS/Typescript, Docker Compose are the common choices.
1st Party Core Micro-Service
Exposes at least 3 packages: a gRPC Service, an App Library and a Scheduled Jobs Library.
gRPC Service is deployment unit that exposes calls that can be consumed by other services or APIs.
App Library is a package that provides a code dependency for other services or APIs to use.
Scheduled Jobs Library is a deployment unit that can be triggered by a jobs runner service to run on a worker node.
A 1st party service may subscribe to an other service's App library as a dependency, in order to use that service.
API service
Exposes at least 2 package: API and App Library.
API Service is deployment unit that exposes http API to Front-End Applications. It provides secure access and orchestration of Core Services. It protects access to Core Services, while providing business/domain functionality.
App Library in this context is a package that provides a dependency for Front-End Applications, 3rd party applications, and other 1st party APIs via the App Libraries they publish.
Front-End Application
Exposes at least 1 package: App (and maybe FE libraries).
App is deployment unit that runs on a web service or is distributed via a Content Delivery Network. it holds the code that runs on the users device, eg HTML, CSS, JS for web or pkg/img for mobile. It provides the user interface and accesses APIs (via the App Libraries they publish) to achieve users goals.
Team Organisation
This whole layout exists to let teams work without colliding, so organise teams around vertical slices, not horizontal layers. A team should own a core service, the API in front of it, and ideally the front-end that consumes it — top to bottom. That way a feature lands inside one team's boundary instead of becoming a relay race across a back-end team, a middleware team and a front-end team.
Conway's Law is unavoidable: your architecture will come to mirror your communication structure whether you plan for it or not. So plan for it. Give each team a clear slice, clear ownership of the packages that slice publishes, and a clear contract — the gRPC Service and App Library — that other teams consume. When the boundaries in the org match the boundaries in the architecture, the stepping-on-each-other problem you adopted microservices to solve actually goes away. When they don't, you've simply distributed the problem across the network.
In short
Get the layers, the repo layout and the team boundaries right at the start and microservices deliver what they promise: teams shipping independently without tripping over each other. Bolt them on without that structure and you get a distributed monolith — all of the cost, none of the autonomy.