Skip to content

Commit

Permalink
GH-872 - Introduce proper identifiers for ApplicationModules.
Browse files Browse the repository at this point in the history
Introduced ApplicationModuleIdentifier that ensures that identifiers chosen for application modules do not contain a double colon (::) as we need those as separator characters for manual dependency declarations referring to named interfaces.
  • Loading branch information
odrotbohm committed Oct 13, 2024
1 parent 5733a27 commit 3af2506
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ public Stream<String> getSharedModuleNames() {
return Arrays.stream(annotation.sharedModules());
}

/*
* (non-Javadoc)
* @see org.springframework.modulith.core.ModulithMetadata#getSharedModuleIdentifiers()
*/
@Override
public Stream<ApplicationModuleIdentifier> getSharedModuleIdentifiers() {
return getSharedModuleNames().map(ApplicationModuleIdentifier::of);
}

/*
* (non-Javadoc)
* @see org.springframework.modulith.model.ModulithMetadata#getSystemName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,21 @@ public NamedInterfaces getNamedInterfaces() {
* Returns the logical name of the module.
*
* @return will never be {@literal null} or empty.
* @deprecated since 1.3, use {@link #getIdentifier()} instead.
*/
@Deprecated
public String getName() {
return source.getModuleName();
return getIdentifier().toString();
}

/**
* Returns the logical identifier of the module.
*
* @return will never be {@literal null}.
* @since 1.3
*/
public ApplicationModuleIdentifier getIdentifier() {
return source.getIdentifier();
}

/**
Expand Down Expand Up @@ -306,7 +318,7 @@ public ArchitecturallyEvidentType getArchitecturallyEvidentType(Class<?> type) {
return getType(type.getName())
.map(it -> ArchitecturallyEvidentType.of(it, getSpringBeansInternal()))
.orElseThrow(() -> new IllegalArgumentException("Couldn't find type %s in module %s!".formatted(
FormattableType.of(type).getAbbreviatedFullName(this), getName())));
FormattableType.of(type).getAbbreviatedFullName(this), getIdentifier())));
}

/**
Expand Down Expand Up @@ -404,11 +416,11 @@ public String toString(@Nullable ApplicationModules modules) {

if (modules != null) {
modules.getParentOf(this).ifPresent(it -> {
builder.append("> Parent module: ").append(it.getName()).append("\n");
builder.append("> Parent module: ").append(it.getIdentifier()).append("\n");
});
}

builder.append("> Logical name: ").append(getName()).append('\n');
builder.append("> Logical name: ").append(getIdentifier()).append('\n');
builder.append("> Base package: ").append(basePackage.getName()).append('\n');

builder.append("> Excluded packages: ");
Expand Down Expand Up @@ -438,8 +450,12 @@ public String toString(@Nullable ApplicationModules modules) {
List<ApplicationModule> dependencies = getBootstrapDependencies(modules).toList();

builder.append("> Direct module dependencies: ");
builder.append(dependencies.isEmpty() ? "none"
: dependencies.stream().map(ApplicationModule::getName).collect(Collectors.joining(", ")));
builder.append(dependencies.isEmpty()
? "none"
: dependencies.stream()
.map(ApplicationModule::getIdentifier)
.map(ApplicationModuleIdentifier::toString)
.collect(Collectors.joining(", ")));
builder.append('\n');
}

Expand Down Expand Up @@ -739,7 +755,7 @@ Classes getClasses() {
}

private String getQualifiedName(NamedInterface namedInterface) {
return namedInterface.getQualifiedName(getName());
return namedInterface.getQualifiedName(getIdentifier());
}

private Collection<ApplicationModule> doGetNestedModules(ApplicationModules modules, boolean recursive) {
Expand Down Expand Up @@ -877,7 +893,7 @@ public static DeclaredDependency of(String identifier, ApplicationModule source,

var target = modules.getModuleByName(targetModuleName)
.orElseThrow(() -> new IllegalArgumentException(
INVALID_EXPLICIT_MODULE_DEPENDENCY.formatted(source.getName(), targetModuleName)));
INVALID_EXPLICIT_MODULE_DEPENDENCY.formatted(source.getIdentifier(), targetModuleName)));

if (WILDCARD.equals(namedInterfaceName)) {
return new DeclaredDependency(target, null);
Expand All @@ -888,7 +904,8 @@ public static DeclaredDependency of(String identifier, ApplicationModule source,
? namedInterfaces.getUnnamedInterface()
: namedInterfaces.getByName(namedInterfaceName)
.orElseThrow(() -> new IllegalArgumentException(
INVALID_NAMED_INTERFACE_DECLARATION.formatted(namedInterfaceName, source.getName(), identifier)));
INVALID_NAMED_INTERFACE_DECLARATION.formatted(namedInterfaceName, source.getIdentifier(),
identifier)));

return new DeclaredDependency(target, namedInterface);
}
Expand Down Expand Up @@ -943,7 +960,7 @@ boolean contains(Class<?> type) {
@Override
public String toString() {

var result = target.getName();
var result = target.getIdentifier().toString();

if (namedInterface == null) {
return result + " :: " + WILDCARD;
Expand Down Expand Up @@ -1230,14 +1247,14 @@ Violations isValidDependencyWithin(ApplicationModules modules) {
.toList();

var targetString = targetNamedInterfaces.isEmpty()
? "module '%s'".formatted(targetModule.getName())
? "module '%s'".formatted(targetModule.getIdentifier())
: "named interface(s) '%s'".formatted(
targetNamedInterfaces.stream()
.map(targetModule::getQualifiedName)
.collect(Collectors.joining(", ")));

var message = "Module '%s' depends on %s via %s -> %s. Allowed targets: %s." //
.formatted(originModule.getName(), targetString, source.getName(), target.getName(),
.formatted(originModule.getIdentifier(), targetString, source.getName(), target.getName(),
declaredDependencies.toString());

return violations.and(new Violation(message));
Expand All @@ -1256,7 +1273,7 @@ Violations isValidDependencyWithin(ApplicationModules modules) {
if (!targetModule.isExposed(target)) {

var violationText = INTERNAL_REFERENCE
.formatted(originModule.getName(), target.getName(), targetModule.getName());
.formatted(originModule.getIdentifier(), target.getName(), targetModule.getIdentifier());

return violations.and(new Violation(violationText + lineSeparator() + description));
}
Expand All @@ -1266,7 +1283,7 @@ Violations isValidDependencyWithin(ApplicationModules modules) {
if (!haveSameParentOrDirectParentRelationship(originModule, targetModule, modules)) {

var violationText = INVALID_SUB_MODULE_REFERENCE
.formatted(originModule.getName(), targetModule.getName(),
.formatted(originModule.getIdentifier(), targetModule.getIdentifier(),
FormattableType.of(source).getAbbreviatedFullName(originModule),
FormattableType.of(target).getAbbreviatedFullName(targetModule));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ public boolean containsModuleNamed(String name) {
Assert.hasText(name, "Module name must not be null or empty!");

return modules.stream()
.map(ApplicationModule::getName)
.map(ApplicationModule::getIdentifier)
.map(Object::toString)
.anyMatch(name::equals);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.core;

import java.util.Objects;

import org.springframework.util.Assert;

/**
* An identifier of an {@link ApplicationModule}.
*
* @author Oliver Drotbohm
* @since 1.3
*/
public class ApplicationModuleIdentifier implements Comparable<ApplicationModuleIdentifier> {

private final String identifier;

/**
* Creates a new {@link ApplicationModuleIdentifier} for the given {@link String}.
*
* @param identifier must not be {@literal null} .
*/
private ApplicationModuleIdentifier(String identifier) {

Assert.hasText(identifier, "Identitifier must not be null or empty!");
Assert.isTrue(!identifier.contains("::"), "Identifier must not contain a double-colon!");

this.identifier = identifier;
}

/**
* Returns the {@link ApplicationModuleIdentifier} for the given source {@link String}.
*
* @param identifier must not be {@literal null}, empty or contain a double colon ({@code ::}).
* @return will never be {@literal null}.
*/
public static ApplicationModuleIdentifier of(String identifier) {
return new ApplicationModuleIdentifier(identifier);
}

/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ApplicationModuleIdentifier o) {
return identifier.compareTo(o.identifier);
}

/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return identifier;
}

/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {

if (obj == this) {
return true;
}

if (!(obj instanceof ApplicationModuleIdentifier that)) {
return false;
}

return Objects.equals(this.identifier, that.identifier);
}

/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(identifier);
}
}
Loading

0 comments on commit 3af2506

Please sign in to comment.