Open ryan-reichenberg opened 1 year ago
… 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.
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
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.
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.
(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?
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.
@mrudolph1986 – It's hard to follow without an example project. Care to provide an example that shows the failure?
@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.
@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.
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;
Hello,
Reading the documentation it states:
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 :)