(Based on Visual Studio 2019 functionality)
In a monolithic application, existing IDE tools can identify every place in the project that a Type/Method/etc is used.
With microservices, we are dealing with many small applications which are still coupled together through API calls or a message bus or something. Existing IDE tools cannot locate everywhere the code is coupled because it treats each application/project as a stand alone piece of code.
I am reduced to using grep/findstr to verify where an API call or message is being used.
This problem may indicate that the microservices have not been divided correctly. Reorganizing the division of tasks may mean that the microservices to not need to call each other, thereby removing the runtime coupling.
I would only consider this a problem between domain-services. It is ok for domain-services to be coupled to utility-services (such as a logging service), because utility-services should be very stable.
aka Ports and Adapters Pattern
See Clean Architecture for details about implementing this design.
Allow an application to equally be driven by users, programs, automated tests, or batch scripts.
And to be developed and tested in isolation from its eventual run-time devices and databases.
Explicitly separate Application, Domain, and Infrastructure.
- Application: the front-end, what the end users interacts with (aka User Side)
- Domain: business logic
- Infrastructure: non-domain things the software depends on, such as database, file system, logging, email (aka Server Side)
Dependencies point only from Application and Infrastructure into the Domain.
Boundaries are isolated by using ports and adapters.
Separation of concerns.
Making the Domain (business logic) the most important and stable part of the system.
Enable isolated testing.
The Domain has no dependencies on anything else in the system.
The Domain defines at least two interfaces: one for the Application to implement and one for the Infrastructure to implement.
The Application and Infrastructure implement the interfaces they need, which gives the Domain control of those parts of the system without the Domain having a code dependency on them.
Domain.IManageCustomers defines FindCustomer(Id) method
Domain.ManageCustomers implements IManageCustomer; this class is private
Domain.ICustomerRepository defines FindCustomer(Id) method
Infrastructure.SqlCustomerRepository implements Domain.ICustomerRepository which actually accesses the database
Application.ConsoleAdapter instantiates Infrastructure.SqlCustomerRepository
Application.ConsoleAdapter instantiates Domain.ManageCustomers(ICustomerRepository) and passes it Infrastructure.SqlCustomerRepository
Application.ConsoleAdapter calls Domain.ManageCustomers.FindCustomer(Id) which causes Domain.ManageCustomer to use its Domain.ICustomerRepository which is actually Infrastructure.SqlCustomerRepository
The Application drives the Domain through an Interface defined in the Domain.
The Domain drives the Infrastructure through an Interface defined in the Domain.
The Interfaces are Explicit Insulators between different parts of the system.
See Dependency Inversion.
In this metaphor, each Interface is a Port.
Each implementation of an Interfaces is an Adapter.
All kinds of Adapters can be interchanged and connected to the Ports.
Imagine a hexagon with the Domain in the center.
There are three sides on the left where different Applications can connect to the Domain (website, console, 3rd party API, etc).
There are three sides on the right where different Infrastructures can connection to the Domain (SQL Server, MongoDb, file system, etc).
This is one way to visualize the benefits of this pattern.
Recommended that you organize your code files by business function rather than by programming category.
Ex: folders for RequestQuote and BindPolicy rather than for Repositories and Controllers.