Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overide existing dependency behaviour #167

Open
ryan-reichenberg opened this issue Mar 21, 2023 · 9 comments
Open

Overide existing dependency behaviour #167

ryan-reichenberg opened this issue Mar 21, 2023 · 9 comments
Assignees
Labels
in: core Core module meta model meta: waiting for feedback Waiting for feedback of the original reporter

Comments

@ryan-reichenberg
Copy link

ryan-reichenberg commented Mar 21, 2023

Hello,

Reading the documentation it states:

an application module’s base package is considered the API package and thus is the only package to allow incoming dependencies from other modules.

However, I like to organise my packages with sub-packages instead of having all my classes in the base package; for example, if I have a common module with a lot of utility classes, I'd like the ability to organise them.

Is there a way Spring Modulith can be configured to allow the package and all of it's subpackages to be included as a dependency without having to define named interfaces for each of the subpackages?

Please let me know if this doesn't make sense :)

@odrotbohm
Copy link
Member

… all of it's subpackages to be included as a dependency …

Can you elaborate on what this means? Can you maybe present the package structure (here, e.g. in Asciiart) and describe which of the packages you'd like to act as what? Note that classes in the sub-packages can freely refer to each other and reach out into the API package. It's only external references that are restricted to only refer to API types.

@odrotbohm odrotbohm self-assigned this Mar 21, 2023
@odrotbohm odrotbohm added in: core Core module meta model meta: waiting for feedback Waiting for feedback of the original reporter labels Mar 21, 2023
@ryan-reichenberg
Copy link
Author

ryan-reichenberg commented Mar 21, 2023

To give concrete examples, I have a profile modules, that contains packages for controllers, services, etc:

app.profile.controllers,
app.profile.services,
etc.

I also have a shared module related to the database, this contains packages for repositories, dtos, etc:

app.database.dtos,
app.database.repositories

Now the profile module depends on the database module; this requires me to define named interfaces in the package-info for the dtos and repositories packages (since those are being directly used), but does this imply I need to define named interfaces for all subpackages I want to expose? What if all subpackages in the database module are required?

Without the named interfaces, it results in errors like:

- Module 'profile' depends on non-exposed type app.songbird.database.dto.UserDTO within module 'database'!
Method <app.songbird.profile.controller.ProfileController.createUser(app.songbird.profile.dtos.UserCreationRequestDTO)> has generic return type <org.springframework.http.ResponseEntity<app.songbird.database.dto.UserDTO>> with type argument depending on <app.songbird.database.dto.UserDTO> in (ProfileController.java:0)
- Module 'profile' depends on non-exposed type app.songbird.database.dto.UserDTO within module 'database'!
UserDTO declares return type UserDTO.createUser(UserCreationRequestDTO) in (UserService.java:0)
- Module 'profile' depends on non-exposed type app.songbird.database.dto.UserDTO within module 'database'!
Method <app.songbird.profile.service.UserService.createUser(app.songbird.profile.dtos.UserCreationRequestDTO)> calls constructor <app.songbird.database.dto.UserDTO.<init>(app.songbird.database.internal.entities.User)> in (UserService.java:24)
- Module 'profile' depends on non-exposed type app.songbird.database.dto.UserDTO within module 'database'!
Method <app.songbird.profile.service.UserService.getUsers(int)> has generic return type <java.util.List<app.songbird.database.dto.UserDTO>> with type argument depending on <app.songbird.database.dto.UserDTO> in (UserService.java:0)
- Module 'profile' depends on non-exposed type app.songbird.database.dto.UserDTO within module 'database'!
Method <app.songbird.profile.service.UserService.createUser(app.songbird.profile.dtos.UserCreationRequestDTO)> has return type <app.songbird.database.dto.UserDTO> in (UserService.java:0)

Please let me know if this still doesn't make sense

@davidkarlsen
Copy link

I have a similar case, where I have:

base.common.mapper <-- some mapper util classes
base.domain.controller
base.domain.service
base.domain.dao

And I have @Modulith(sharedModules = "common") on the Main class.

I even have a package file:

@ApplicationModule(allowedDependencies = {"common"})
package base.domain;

import org.springframework.modulith.ApplicationModule;

but still validation fails with:

org.springframework.modulith.core.Violations: - Module 'domain' depends on module 'common' via base.domain.service.GeneratedChargesMapper -> base.common.mapper.MapperUtil. Allowed targets: common.

@odrotbohm
Copy link
Member

I can only recommend reading up on the application module fundamentals in the reference documentation. Both your examples do not actually model functional modules. domain is a generic term, not a functional unit.

@davidkarlsen – In your case, everything works as expected, but I admit the error message is misleading. b.c.mapper is an internal package of the common “module”. Thus, other modules (your domain one) are not allowed to access those. To make mapper accessible to other modules, it needs to be exposed as named interface. But as indicated above, the arrangement you start fundamentally subverts the idea of functional separation as the package structure is completely dominated by technical layering.

@davidkarlsen
Copy link

(domain is just to anonymize the app). But isn't shared modules supposed to cater for this?
The error message is confusing:
org.springframework.modulith.core.Violations: - Module 'domain' depends on module 'common' via base.domain.service.GeneratedChargesMapper -> base.common.mapper.MapperUtil. Allowed targets: common.

common is not allowed? But is listed in allowed target?

@mrudolph1986
Copy link

mrudolph1986 commented Aug 25, 2023

Having the same problem.

I want to structure my shared module by creating packages, but still depend on them. I don`t want to be forced to split it into multiple shared modules.

Internal packages of a shared module, like "mapper" are not exposed via NamedInterface. The DeclaredDependencies of shared modules contain only the unnamed NamedInterface

I can not define explicit references to NamedInterfaces in shared at a global level (Modulithic->sharedModules) like in ApplicationModule->allowedDependencies

Also as mentioned before the generated violation-message is very confusing:

org.springframework.modulith.core.Violations: - Module 'shared' depends on module 'shared' via Class -> AnotherClass. Allowed targets: shared.

@odrotbohm
Copy link
Member

@mrudolph1986 – It's hard to follow without an example project. Care to provide an example that shows the failure?

@mrudolph1986
Copy link

@odrotbohm I have created a running example: https://github.com/mrudolph1986/modulith167

The test fails with:

org.springframework.modulith.core.Violations: - Module 'moduleA' depends on module 'common' via com.example.demo.moduleA.SomeA -> com.example.demo.common.mapper.Mapper. Allowed targets: moduleB, common.
- Module 'moduleA' depends on module 'common' via com.example.demo.moduleA.SomeA -> com.example.demo.common.mapper.Mapper. Allowed targets: moduleB, common.
- Module 'moduleA' depends on module 'common' via com.example.demo.moduleA.SomeA -> com.example.demo.common.mapper.Mapper. Allowed targets: moduleB, common.

@marinusgeuze
Copy link

@odrotbohm, I'm encountering the same issue as @ryan-reichenberg mentioned in the initial comment. In my situation, there's a module relation from the 'household' module to the 'budgetdefinition' module, both of which are functional modules. Within my 'budgetdefinition' module, I've established a well-organized package structure for different types of classes, enhancing the overall code structure within the module. However, this approach requires me to define NamedInterfaces for each package and add them as allowed packages in my 'household' module.

Below, you'll find some output and code snippets for reference. Is there a more elegant solution to address this? NOTE: This is an example project with only a few classes, but in our production code we have many more commands, events, projections, etc in one module.

Budgetdefinition

Logical name: budgetdefinition
Base package: nl.householdfinancialsystem.budgetdefinition
Named interfaces:

  • NamedInterface: name=<>, types=[]
  • NamedInterface: name=aggregate, types=[ n.h.b.d.a.BudgetDefinition ]
  • NamedInterface: name=command, types=[ n.h.b.d.c.AddBudgetDefinition ]
  • NamedInterface: name=event, types=[ n.h.b.d.e.BudgetDefinitionAdded ]
  • NamedInterface: name=query, types=[ n.h.b.d.q.BudgetDefinitionProjection ]
  • NamedInterface: name=service, types=[ n.h.b.d.s.BudgetDefinitionCommandService, n.h.b.d.s.BudgetDefinitionQueryService ]

@org.springframework.modulith.ApplicationModule(
allowedDependencies = {
"budgetdefinition::aggregate",
"budgetdefinition::command",
"budgetdefinition::event",
"budgetdefinition::query",
"budgetdefinition::service",
})
package nl.householdfinancialsystem.household;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Core module meta model meta: waiting for feedback Waiting for feedback of the original reporter
Projects
None yet
Development

No branches or pull requests

5 participants