「 -私たちはソフトウェアを開発する場合、我々は、作成したい背骨を」:私は見背骨、保守性の背骨を伸ばす、背骨を、そして-今の傾向に-分解(mikroservisyにモノリスを拡大する能力、必要に応じて)。お気に入りのアビリティスパインのリストに追加してください。」
これらの「機能」のほとんど(おそらくすべて)は、コンポーネント間の純粋な依存関係と密接に関連しています。
コンポーネントが他のすべてのコンポーネントに依存している場合、1つのコンポーネントを変更するとどのような副作用が発生するかわからないため、コードベースの保守が困難になり、拡張と分解がさらに困難になります。
時間の経過とともに、コードベースのコンポーネントの境界はぼやける傾向があります。悪い依存関係が表示され、コードの操作が難しくなります。これはあらゆる種類の悪い結果をもたらします。特に、開発は減速しています。
ドメイン駆動型設計の専門用語を使用するために、多くの異なるビジネス領域または「制限されたコンテキスト」にまたがるモノリシックコードベースで作業している場合、これはさらに重要です。
不要な依存関係からコードベースを保護するにはどうすればよいですか? 境界のあるコンテキストを注意深く設計し、コンポーネントの境界を常に順守します。 この記事では、SpringBootを使用するときに両方の場合に役立つ一連のプラクティスを示します。
サンプルコード
この記事には、GitHubのサンプル作業コードが付属しています 。
パッケージ-プライベートビジビリティ
コンポーネントの境界を維持するのに何が役立ちますか?視認性の低下。
「内部」クラスにPackage-Private可視性を使用する場合、同じパッケージ内のクラスのみがアクセスできます。 これにより、パッケージの外部から不要な依存関係を追加することが困難になります。
, , . ?
, .
, .
, , .
! , , . , , , . !
, , package-private , , , .
? package-private . , package-private , , ArchUnit , package-private .
. , , :

. .
Domain-Driven Design (DDD): , . , . «» « » .
, . .
: , . . public , , .
API
, :
billing
├── api
└── internal
├── batchjob
| └── internal
└── database
├── api
└── internal internal, , , , api, , , API, .
internal api :
, internal package-private. public ( public, ), .
, Java package-private , , .
.
Package-Private
database:
database
├── api
| ├── + LineItem
| ├── + ReadLineItems
| └── + WriteLineItems
└── internal
└── o BillingDatabase+, public, o, package-private.
database API ReadLineItems WriteLineItems, , . LineItem API.
database , BillingDatabase :
@Component
class BillingDatabase implements WriteLineItems, ReadLineItems {
...
}, .
, .
api, internal, . internal , , api.
database, , , .
batchjob:
batchjob API . LoadInvoiceDataBatchJob(, , ), , WriteLineItems:
@Component
@RequiredArgsConstructor
class LoadInvoiceDataBatchJob {
private final WriteLineItems writeLineItems;
@Scheduled(fixedRate = 5000)
void loadDataFromBillingSystem() {
...
writeLineItems.saveLineItems(items);
}
} , @Scheduled Spring, .
, billing:
billing
├── api
| ├── + Invoice
| └── + InvoiceCalculator
└── internal
├── batchjob
├── database
└── o BillingService
billing InvoiceCalculator Invoice. , InvoiceCalculator , BillingService . BillingService ReadLineItemsAPI - :
@Component
@RequiredArgsConstructor
class BillingService implements InvoiceCalculator {
private final ReadLineItems readLineItems;
@Override
public Invoice calculateInvoice(
Long userId,
LocalDate fromDate,
LocalDate toDate) {
List<LineItem> items = readLineItems.getLineItemsForUser(
userId,
fromDate,
toDate);
...
}
}, , , .
Spring Boot
, Spring Java Config Configuration internal :
billing
└── internal
├── batchjob
| └── internal
| └── o BillingBatchJobConfiguration
├── database
| └── internal
| └── o BillingDatabaseConfiguration
└── o BillingConfiguration
Spring Spring .
database :
@Configuration
@EnableJpaRepositories
@ComponentScan
class BillingDatabaseConfiguration {
} @Configuration Spring, , Spring .
@ComponentScan Spring, , , ( ) @Component . BillingDatabase, .
@ComponentScan @Bean @Configuration.
database Spring Data JPA. @EnableJpaRepositories.
batchjob :
@Configuration
@EnableScheduling
@ComponentScan
class BillingBatchJobConfiguration {
} @EnableScheduling. , @Scheduled bean-LoadInvoiceDataBatchJob.
, billing :
@Configuration
@ComponentScan
class BillingConfiguration {
} @ComponentScan , @Configuration Spring bean-.
, Spring .
, , @Configuration. , :
()
SpringBootTest.() ,
@Conditional...., , () , () .
: billing.internal.database.api public, billing, .
, ArchUnit.
ArchUnit
ArchUnit - , . , , .
, internal . , billing.internal.*.api billing.internal.
internal , - «».
( «internal» ), , @InternalPackage:
@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InternalPackage {
} package-info.java :
@InternalPackage
package io.reflectoring.boundaries.billing.internal.database.internal;
import io.reflectoring.boundaries.InternalPackage;, , .
, , :
class InternalPackageTests {
private static final String BASE_PACKAGE = "io.reflectoring";
private final JavaClasses analyzedClasses =
new ClassFileImporter().importPackages(BASE_PACKAGE);
@Test
void internalPackagesAreNotAccessedFromOutside() throws IOException {
List<String> internalPackages = internalPackages(BASE_PACKAGE);
for (String internalPackage : internalPackages) {
assertPackageIsNotAccessedFromOutside(internalPackage);
}
}
private List<String> internalPackages(String basePackage) {
Reflections reflections = new Reflections(basePackage);
return reflections.getTypesAnnotatedWith(InternalPackage.class).stream()
.map(c -> c.getPackage().getName())
.collect(Collectors.toList());
}
void assertPackageIsNotAccessedFromOutside(String internalPackage) {
noClasses()
.that()
.resideOutsideOfPackage(packageMatcher(internalPackage))
.should()
.dependOnClassesThat()
.resideInAPackage(packageMatcher(internalPackage))
.check(analyzedClasses);
}
private String packageMatcher(String fullyQualifiedPackage) {
return fullyQualifiedPackage + "..";
}
} internalPackages(), reflection , @InternalPackage.
assertPackageIsNotAccessedFromOutside(). API- ArchUnit, DSL, , «, , , ».
, - public .
: , (io.reflectoring ) ?
, ( ) io.reflectoring. , .
, .
, :
class InternalPackageTests {
private static final String BASE_PACKAGE = "io.reflectoring";
@Test
void internalPackagesAreNotAccessedFromOutside() throws IOException {
// make it refactoring-safe in case we're renaming the base package
assertPackageExists(BASE_PACKAGE);
List<String> internalPackages = internalPackages(BASE_PACKAGE);
for (String internalPackage : internalPackages) {
// make it refactoring-safe in case we're renaming the internal package
assertPackageIsNotAccessedFromOutside(internalPackage);
}
}
void assertPackageExists(String packageName) {
assertThat(analyzedClasses.containPackage(packageName))
.as("package %s exists", packageName)
.isTrue();
}
private List<String> internalPackages(String basePackage) {
...
}
void assertPackageIsNotAccessedFromOutside(String internalPackage) {
...
}
} assertPackageExists() ArchUnit, , , .
. , , . , @InternalPackage internalPackages().
, .
Java- Spring Boot ArchUnit , - .
API , .
!
, , GitHub .
Spring Boot, moduliths.