عندما نقوم بتطوير البرمجيات، ونحن نريد لخلق "- العمود الفقري ": أرى العمود الفقري ، والصيانة العمود الفقري ، تمديد العمود الفقري ، و- في الاتجاه الآن - التحلل (القدرة على توسيع متراصة على mikroservisy، إذا لزم الأمر). أضف إلى قائمة " العمود الفقري للقدرة" المفضل لديك .
معظم - وربما كل - هذه "الميزات" تسير جنبًا إلى جنب مع التبعيات الخالصة بين المكونات.
إذا كان أحد المكونات يعتمد على جميع المكونات الأخرى ، فنحن لا نعرف الآثار الجانبية لتغيير مكون واحد ، مما يجعل من الصعب الحفاظ على قاعدة الكود ويزيد من صعوبة توسيعها وتفككها.
بمرور الوقت ، تميل حدود المكونات في قاعدة البيانات إلى التعتيم. تظهر التبعيات السيئة ، مما يجعل العمل مع الكود أكثر صعوبة. هذا له كل أنواع العواقب السيئة. على وجه الخصوص ، فإن التنمية تتباطأ.
تزداد أهمية هذا الأمر إذا كنا نعمل على قاعدة بيانات متجانسة تمتد عبر العديد من مجالات الأعمال المختلفة أو "السياقات المحدودة" لاستخدام مصطلحات التصميم المستند إلى المجال.
كيف يمكننا حماية قاعدة الرموز الخاصة بنا من التبعيات غير المرغوب فيها؟ مع التصميم الدقيق للسياقات المحدودة والالتزام المستمر بحدود المكونات. توضح هذه المقالة مجموعة من الممارسات التي تساعدك في كلتا الحالتين عند العمل مع Spring Boot.
عينة من الرموز
هذه المقالة مصحوبة بنموذج رمز العمل على 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.