The basic technique is to use modular architecture.
You divide your application into separate problems each represented by one or more modules. You create API for these modules to talk to each other.
You also create some project-wide guidelines for application architecture, so that the modules coexist as good neighbors.
You then have separate teams responsible for one or more modules.
If your application is large enough you might consider building some additional internal framework, for example plugin mechanism.
For example, if your application is an imaginary banking system that takes care of users' accounts, transactions and products they have, you might have some base framework (which is flows of data in the application, events like pre/post date change, etc.) and then you might have different products developed to subscribe to those flows of data or events and act upon the rest of the system through internal APIs.
Perfectly described! People forget or don't know that there are many different architecture across monolithic systems, or that there are something in between monolith and microservice.
A monorepo I guess.
It looks like Microsoft is using a monorepo for their office applications (https://rushjs.io/) and you could do the same thing for node.js using yarn workspaces/lerna/rush.
Each "Microservice" could live in a separate package which you can import and bundle into single executable.
Well for starters, each component's API can be published and versioned separately from its implementation.
The build of a component would only have access to the API's of the other components (and this can include not having knowledge of the container it runs in).
The implementation can then change rapidly, with the API that the other teams develop against moving more slowly.
Even so, code reviews can be critical. The things to look out for (and block if possible) are hidden or poorly defined parameters like database connections/transactions, thread local storage and general bag parameters.
In some languages dependency injection should be useful here. Unfortunately DI tools like Spring can actually expose the internals of components, introduce container based hidden parameters and usually end up being a versioned dependency of every component.
You divide your application into separate problems each represented by one or more modules. You create API for these modules to talk to each other.
You also create some project-wide guidelines for application architecture, so that the modules coexist as good neighbors.
You then have separate teams responsible for one or more modules.
If your application is large enough you might consider building some additional internal framework, for example plugin mechanism.
For example, if your application is an imaginary banking system that takes care of users' accounts, transactions and products they have, you might have some base framework (which is flows of data in the application, events like pre/post date change, etc.) and then you might have different products developed to subscribe to those flows of data or events and act upon the rest of the system through internal APIs.