Skip to content

urgoringo/microservice-design-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 

Repository files navigation

(Highly) Opinionated (Microservice) Design Guide

Following are the ideas I have gathered while working with microservices at Wise (former Transferwise).

Why not to build microservices

TODO

Ideas on the macro design

Microservice size

There are 2 factors to consider when deciding how big/small should a service be: technical scalability and organisational scalability. If neither of these is pushing for a split it is better to keep going in a monolith. There is no reason to assume that if we are unable to design things well in a single codebase we will be able to design things well in a distributed codebase. Read more

Cross-service communication

If you indeed need to split things into microservices be careful not to replicate the same antipatterns you might have in the current (legacy) codebase. Otherwise, you may just replace the monolith with a distributed monolith.

Having as few synchronous calls between services as possible is always something to strive for. Using Kafka to build local replicas of data is one solution. Often Kafka is an obvious solution where we need complex data transformation or aggregation of multiple sources. However, it is also very handy for avoiding single source sync data fetching as well.

Ideas on the micro design

How to name things

Avoid technical suffixes like Service, Gateway, Repository and instead search for the domain specific names. Names are very powerful - just like they can work as central cores for bringing more order into things they can also make things messier and harder to understand.

Fool proof design

Try to design your domain model so that it is impossible to use it incorrectly.

One such example is using separate classes for different entity states

Plain JDBC as opposed to ORMs

There are many situations when using Hibernate is not a good idea. For very simple domain models that clearly map to DB tables ORM may be OK. However, when domain model grows in complexity there will be more and more constraints from using ORM.

There are some more lightweight tools for accessing relational DBs like jOOQ or myBatis. Don't have any first-hand experience in using these, but I recommend thinking carefully if the benefits of these libraries are worth the potential constraints they may be imposing for more complex use cases.

An idea how to manage complex data mapping to domain model using specialised factories

Avoid unnecessary horizontal layers

Often there is separate Service class for each Entity which then encapsulates all access to that Entity. If there is no additional logic in that Service then it should not exist. Relaxed layering FTW!

Javaconfig based DI as opposed to annotations

The benefits are written here.

Hardcoded config as opposed to externalized config

Whenever possible prefer using config as is inside code as opposed to external. Read this post for more details

Null object

Always avoid any nulls. Instead use NullObject and Optional. Avoid NotNull annotations and instead use explicit Option types.

Code packages

One option how to package is following the Hexagonal architecture. Top level will have all the feature packages if the service has more than one module. I think it is perfectly valid that a microservice contains multiple modules. No need to split the system into powdery pieces as noted in [Microservice size]

Then under each module package we can have all stuff needed for that specific module. Note that this approach also prepares us for possible future split if the team or team cognitive load grows too big.

Good resource for pragmatic Hexagonal architecture: Getting your hands dirty on Clean Architecture.

Testing

Categorisation

Try to come up with clear categories how your team classifies tests. Otherwise, you may end up with all sorts of weird creations that do some "unit test" things and some "integration test" things all at once. Not having clarity means more work when deciding what tests are needed for any new feature but also harder time understanding existing tests.

Unit tests

TDD is good, but it is also very hard. Even when failing to do TDD by the book I have found it valuable to listen to what the tests are trying to tell me. If you do that then unit tests can be great source for pushing our design.

If better design by tests is not something you want then it might be better to focus on writing more coarse-grained tests. In case of Hexagonal architecture port level tests are good for driving high level architecture.

In any case I recommend not writing tests against configuration.

Acceptance tests

In Growing OO Software Guided by Tests Steve and Nat suggest the idea of starting with a high level acceptance test before getting into new feature implementation. One easy way of knowing when you have enough acceptance tests is when you don't feel like you should do some manual testing before releasing.

One question with acceptance tests is the level at which these should be written. Steve and Nat suggest writing them as e2e as possible. However, sometimes it can be more effective to write these tests at your port level (or whatever is the facade to the domain model). It is just much more convenient to write tests through Java API than pushing everything through some kind of HTTP API. To push it even further we can write acceptance tests without any framework dependency. We don't really need Spring for verifying our business logic (at least hopefully we don't). For wiring things together we can just instantiate our Java config objects and get the port instance without Spring.

Other resources:

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published