TL;DR I had an idea that we could build modular monoliths (taking the architectural example of microservices), by separating domain modules that run in separate goroutines and communicate strictly trough channels.

I’ve been thinking about why the microservices are nowadays all the rage. Services separate the domain not just logically, but also physically. They are taking the advantage from the “network separation” and therefore you cannot communicate with an service otherwise than trough its public API. Also teams can be specialized on just one or few services/domains. And we all know how specialization is good for our society :)

But microservices come with an cost of difficult deployment, network latency, distributed transactions etc. IMHO 90% of projects don’t need to run on an distributed cluster and manage logging and monitoring of multiple instances of some service (and also 76.8% of statistics are made up on the spot).

So I’ve come up with an concept of an system architecture that mimics the microservices environment, but gets all the network and deployment problems out of the way by using the all known and convenient monolith approach :)

By using golang’s goroutines and channels we can build separate “services” that run in its own goroutine and have an public API composed of multiple channels for communicating with the service.

Every service would have it’s own package (that can’t be included by other packages). Also only public things of the package would be the ‘constructor’ of the service struct and the service would have an Run() method which spins up the go routine that listens on the endpoints (channels) for requests.

An example HelloService, that has one endpoint which takes an name and returns back an “hello name” string, looks like this:

Everything from the public view looks like this is an service that could run very easily on another node (in fact, this code needs just a thin network layer to be ported to another node). We can send requests to the service, the service responds asynchronously, and other clients cant mess with the internals of the service. Messing with the objects internal to the service is the main reason when the project gets tangled and very hard to modify.

Side note: The bounding of domains in an monolithic project can be done also with other ways and they don’t have run in other goroutines to do so. You can use just simple package and there carefully select methods and structs that are exported. Or use a tool that checks the graph of the dependencies. But I think that this “goroutines + channels as a service” approach is a good way to force some people that apparently need to have some physical boundaries to don’t mess across the domains.