Heard of the Single Responsibility principle? It's basically the same thing but instead of applying it to a single class, we apply it to an interconnected set of code - A bounded context (to borrow a term from Domain Driven Design).
Similarly to a class, as a module you want your code to be:
- lowly coupled - be careful about how many and what kind of dependencies you create
- highly cohesive - you want the code in your module to be closely related
Many programming languages by default have a mechanism of logically grouping code together.
In C# for example, we have namespaces. In Java, packages.
You isolate this portion of code and give it a name that represents the purpose of it, being very careful as to what you make it dependent on since any external dependency could be coupling your module to something else.
If an external module needs to communicate, then you would define a public interface as a means of facilitating that need!
Because it logically breaks your system into smaller, isolated chunks instead of having one big ball of spaghetti! If all your code is allowed to be referenced anywhere, regardless of the business context, it becomes extremely difficult to follow.
You can (and should!) create modular software in any application - but it can be challenging if you have a single codebase since there are no actual physical restrictions about where to put your code.
Someone can easily break the design principles of the module since you can simply reference other portions of the code and bring them in.
Also, don't forget about the database! In a single codebase, generally, there's also a single database - which means that even though your code can be pristinely separated, your queries can now create dependencies across modules since you can join across different contexts!
Microservices helps this scenario by creating a physical restriction since the code and the database becomes divided up into smaller, isolated portions.
You can take a module (or a few!), turn this into a microservice and now it's not so easy to add a piece of code from somewhere else in the system from where it doesn't belong. Still possible, but not as simple as joining a table or importing a new namespace!
Not only that but since each microservice must have its own database, you cannot query across different domains either.