Designing a java project for monoliths and microservices at the same time

I would like to know how you will separate project modules in java for monolith with the ability to convert modules to micro-services later?
My personal designation is as follows:

com.company.shopapp.product ...product.domain (ddd, services, repositories, entities, aggregates, command handlers - everything with package scope) ...product.api (everything with public scope) ...product.controller (CQRS endpoints for commands in web perspective - (package scope)) ...product.query(CQRS - package scope) com.company.shopapp.sales - domain - api - controller - query 

Here we have mainly a product management context and a sales context as packages.

Modules communicate with each other using only public interfaces (api package). In my project, I use "..api.ProductFacade" to centralize the points of connection.

When my "sales" module grows, I will turn it into a microservice, implementing the "..api.ProductFacade" interface as a "rest" or "soap box" client, and on the other hand I will create an Endpoint / RestController based on the ProductFacade interface. The package "com.company.shopapp.product.api" will be converted into an extended library and added to both projects.

Edit: I can achieve this out of the box using the @Feign library. https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#spring-cloud-feign-inheritance

The whole idea seems nice, but maybe you have a better way to design a project and ensure that its breakdown into microservices does not violate the whole application.

+5
source share
3 answers

I think your module structure is good. But I suggest you create a real project with several modules ( link ). Thus, using a code from another module, a compilation error will be generated. This will help you keep your good intentions!

To do this, you will have to separate each module into a closed (implementation) and open (api, only interfaces) module (doing this, you do not need the โ€œapiโ€ package. The implementation module can depend on any public module, but not a private module.

If you connect your application together in a private module, with dependency injection, private modules will not have "internal" dependencies! Private modules will not have any dependencies < compilation time , only dependencies run time .

Here's a quick modular dependency graph: enter image description here

Hope you find this useful!

Edit: You will need an additional module only to download the application!

+2
source

TL; DR: Think about components and modules separately and set their โ€œtouch pointsโ€

The modules, as in your example, look like an end-to-end structure that is in good agreement with the recommended microservice practice. Thus, all of them can be part of one microservice. And if you intend to use DDD, you will want to include the limited context name in your package path.

In my own source code, I usually separate (at the top level) modules like config (for loading and analyzing, well, config), functional for the functional kernel, operational domain model for concurrency control, Akka chord structure, monitoring, etc. and adapters , where all the API, DB, and MQ codes live. And finally, the app module, where everything is running and the interfaces are tied to implementations. In addition, you usually have utils or commons for lower level templates, algorithms, etc.

In some architecture schools, there is a clear separation between modules and components. Although the former are part of the source code structure, the latter are runtime units that consume resources and live their own way.

In your case, microservices correspond to such components. These components can work in one JVM, and you get a monolith. Or they can run in a separate JVM on a (possibly) separate host. Then you call them microservices.

So you need to:

  • make the source code of each component standalone so that it can run in a separate execution space (for example, a class loader, thread, threadpool, actor system subsystem). Therefore, you need a launcher to revive it. Then you can call from your public static void main(...) .
  • enter some modules into your code that will contain the semantics of an individual component. So that you can understand the scope of the component from the code.
  • An abstract connection between components so that you can use adapters (source code modules) to talk over the network or use mechanisms inside the JVM, such as calling a procedure or sending an Akka message.

I should note that at the lower levels you can use common source code modules in your components so that they can have some intersections in the code. But at a higher level, the source code will be distinctive, so you can separate it into modules according to the components.

You can use Akka and run each of your components in the supervision subtree, where the subtree supervisor is your main component of your component. Then this basic definition of an actor will be your main component module. If you need to pass components, you must pass the corresponding ActorRefs to the adapters as a configuration parameter.

You are talking about the centralization of communication points, but, in my opinion, if you adhere to the microservices paradigm and a high level of autonomy for your components, then for every API call someone must have a contract. Enter various interaction templates with a limited DDD context. If you leave it in some centrally managed module that each component should use, then this is an API control case. As long as you are the only supportive, this can be convenient. But when different developers (or even teams) accept their roles, you will need to make this decision again, taking into account the new conditions.

When you later separate the components, you will pass the URL to the adapters instead of ActorRefs.

+2
source

Microservice connection by functionality and degree of connection. I used this approach:

  • com.company.product:
    • great service possible:
      • configuration
      • services
      • domain
      • etc.
    • Possible second great service:
      • configuration
      • services
      • domain
      • etc.
    • configuration
    • services // which will probably never be shared
    • domain // common domain
    • etc.

When splitting a project, you analyze new common dependencies that see packages, exclude the shared library, copy the project for each microservice, delete unnecessary code, possibly change service implementations (for example, if the "possible large service" uses the "possible second large service",), configuration and assembly. โ€œBigโ€ in this context means a full-fledged functional implementation of something that can be scaled horizontally or needs to be a microservice for other reasons.

+2
source

Source: https://habr.com/ru/post/1273708/


All Articles