프로젝트 구조화

Lino는 첫 commit부터 아키텍처 결정이 명확히 드러나도록 .NET solution을 구조화합니다. 생성된 project는 Aspire 오케스트레이션, 공유 블록, 테스트, 코드 품질 구성, 그리고 각 service, module, API contract, persistence concern, integration boundary가 놓일 명확한 위치를 갖춘 상태로 시작됩니다.


이 섹션에서는 lino project new, lino service new, lino module new가 solution의 형태를 어떻게 잡는지 설명합니다. 목표는 단순히 폴더를 만드는 것이 아니라 runtime boundary, 데이터베이스 ownership, module isolation, 그리고 simple service에서 modular monolith 또는 distributed system으로 발전하는 경로를 정의하는 것입니다.

Solution foundation 생성

lino project new command는 새 .NET solution의 technical foundation을 생성합니다. CLI를 설치하고 인증한 뒤 빈 directory에서 실행합니다.

lino project new --name <ProjectName>

<ProjectName> argument는 solution의 실제 이름을 나타냅니다. 이 이름은 namespace, assembly, path, configuration, artifact, component 간 reference의 일부가 되므로 짧고 안정적이며 대표성 있는 이름을 선택합니다.

Interactive wizard는 전체 solution의 structure와 runtime behavior에 영향을 주는 결정을 요청합니다.

  • Project namespace: 생성된 project가 사용하는 root technical identity입니다.
  • Display name: 생성된 metadata와 user-visible point에서 사용하는 친숙한 이름입니다.
  • Language and stack: 현재 C# with .NET 10 and Aspire입니다.
  • Code analyzers: bootstrap부터 consistency와 quality를 유지하기 위해 shared package와 rule을 활성화합니다.
  • CQRS: 선택된 mediator library로 orchestration하면서 application layer가 command와 query를 분리하도록 준비합니다.
  • Base classes in the solution: common abstraction을 solution 내부에 local로 생성할지 제어합니다.
  • Distributed cache: Microsoft.Extensions.Caching.Hybrid가 local instance memory만 사용할지, Aspire가 구성한 Redis 기반 distributed layer도 사용할지 정의합니다.
  • Asynchronous communication: RabbitMQ with MassTransit 및 integration event에 사용하는 messaging/outbox block을 활성화합니다.
  • Data language: 생성 중 domain metadata를 설명하는 데 사용할 language입니다.
  • Application supported cultures: UI text, validation, error, API response를 위한 localization resource를 생성합니다.
  • Default culture: application이 fallback을 필요로 할 때 사용하는 primary language입니다.

확인 후 Lino는 성장에 맞게 정리된 solution을 생성합니다. 최소 project는 Aspire, shared layer, Shared 영역의 test와 함께 시작됩니다.

<ProjectName>/
├── <ProjectName>.slnx
├── Directory.Build.props
├── Directory.Packages.props
├── src/
│   ├── Aspire/
│   │   ├── AppHost/
│   │   │   └── <ProjectName>.AppHost.csproj
│   │   └── ServiceDefaults/
│   │       └── <ProjectName>.ServiceDefaults.csproj
│   └── Services/
│       └── Shared/
│           ├── Api/
│           │   └── <ProjectName>.Shared.Api.csproj
│           ├── Application/
│           │   └── <ProjectName>.Shared.Application.csproj
│           ├── Domain/
│           │   └── <ProjectName>.Shared.Domain.csproj
│           ├── Infrastructure/
│           │   └── <ProjectName>.Shared.Infrastructure.csproj
│           ├── Infrastructure.Persistence/
│           │   └── <ProjectName>.Shared.Infrastructure.Persistence.csproj
│           └── Integration.Events/          (when messaging exists)
│               └── <ProjectName>.Shared.Integration.Events.csproj
└── tests/
    └── Services/
        └── Shared/
            └── UnitTests/
                ├── Domain/
                │   └── <ProjectName>.Shared.Domain.UnitTests.csproj
                └── Application/
                    └── <ProjectName>.Shared.Application.UnitTests.csproj

Shared project의 역할

Shared는 solution이 공유하는 platform code를 포함합니다. 이것은 business service가 아닙니다. Cross-cutting abstraction, common error, localization infrastructure, base application contract, persistence helper, API extension, host integration, observability, 재사용 가능한 technical utility를 위해 이 공간을 사용합니다.

Shared에 business rule을 넣지 마세요. Rule이 application의 특정 capability에 속한다면 그 책임을 소유한 service 또는 module 안에 있어야 합니다. Shared의 목적은 technical repetition을 줄이는 것이지 서로 다른 domain을 결합하는 shortcut이 되는 것이 아닙니다.

Aspire와 infrastructure decision

AppHost는 solution과 그 runtime resource를 compose합니다. Redis, RabbitMQ, SQL Server, PostgreSQL, Redis Insight, service, WebApp 또는 worker가 추가되면 Aspire가 local orchestration point가 됩니다. 이는 development 중 execution, service discovery, log, metric, trace, resource visualization을 단순화합니다.

ServiceDefaults는 service discovery, health check, resilience, logging, metric, tracing, OpenTelemetry integration 같은 hosting pattern을 중앙화합니다. 각 service가 이를 개별로 구성하는 대신 solution은 공통 composition point와 함께 시작됩니다.

Code analyzer

Static code analyzer는 development 중 code를 검사하고 실행 전에 문제를 드러냅니다. Style inconsistency, fragile pattern, refactoring opportunity, possible bug, security alert가 여기에 포함됩니다.

lino project new에서 analyzer를 활성화하면 solution은 StyleCop.Analyzers, SonarAnalyzer.CSharp, Roslynator.Analyzers 같은 package가 중앙에서 구성된 상태로 시작됩니다. 이로써 각 project가 자체 quality baseline을 따로 결정하지 않아도 됩니다.

  • Quality improvement: code를 readable하고 consistent하게 유지하며 solution standard에 맞춥니다.
  • Error prevention: 문제가 manual testing이나 production에 도달하기 전에 일찍 지적합니다.
  • Standardization: service, module, team 간 style 차이를 줄입니다.
  • Assisted refactoring: C# code에서 가능한 simplification과 modernization opportunity를 강조합니다.

Distributed cache, hybrid cache, Redis

Lino는 HybridCache를 통해 cache operation을 중앙화하는 Microsoft library인 Microsoft.Extensions.Caching.Hybrid를 사용할 수 있도록 solution을 준비합니다. 이 abstraction을 통해 handler, application service, infrastructure component는 cache implementation detail을 code 전체에 퍼뜨리지 않고 query result, permission, setting, support data를 저장할 수 있습니다.

Project 생성 중 distributed cache를 활성화하지 않으면 HybridCache는 계속 사용할 수 있지만 실행 중인 instance의 local memory에만 제한됩니다. 이 mode는 local scenario, 작은 environment 또는 single replica application에는 단순하고 충분하지만 각 process가 자체 cache를 유지하며 data는 instance 간에 공유되지 않습니다.

Distributed cache를 활성화하면 Lino는 Aspire resource에 Redis를 추가하고 shared cache layer를 사용하도록 infrastructure를 구성합니다. Redis를 사용하면 같은 service의 여러 instance가 같은 layer를 조회할 수 있어 database, internal API, integration에 대한 반복 read를 줄이고 horizontal scale에 대비할 수 있습니다.

  • Performance: 반복 read와 support operation의 response time을 줄입니다.
  • Scalability: 서로 다른 instance가 cached data를 공유할 수 있게 합니다.
  • Availability: primary database의 read pressure 일부를 분리합니다.
  • Operational cost: high-volume query와 integration에서 반복 processing을 줄입니다.

이 결정은 environment foundation을 바꾸기 때문에 lino project new에서 이루어집니다. AppHost resource, local secret, package, infrastructure configuration, Aspire dashboard에 표시되는 execution topology가 변경됩니다.

Asynchronous communication

Asynchronous communication은 service, module, component가 main operation flow를 block하지 않고 system fact에 반응할 수 있게 합니다. Producer가 consumer의 즉시 가용성에 의존하면 안 되거나 notification, projection, external integration, context 간 synchronization 같은 후속 effect를 trigger해야 할 때 특히 유용합니다.

Asynchronous communication을 활성화하면 Lino는 Aspire에 RabbitMQ를 추가하고 integration event에 사용하는 messaging 및 outbox block과 함께 MassTransit을 구성합니다. Service 또는 module이 publish한 event는 Integration.Events의 explicit contract로 표현되며 더 resilient하게 처리될 수 있습니다.

  • Performance: 모든 consumer가 끝날 때까지 기다리지 않고 main use case를 계속 진행할 수 있게 합니다.
  • Scalability: consumer와 queue로 processing을 분산하여 spike를 더 제어 가능하게 흡수합니다.
  • Resilience: outbox와 함께 사용할 때 reprocessing을 가능하게 하고 message loss를 줄입니다.
  • Decoupling: 즉시 response가 필요하지 않을 때 producer와 consumer 사이의 direct dependency를 피합니다.

Consumer failure가 producer transaction을 되돌리면 안 되는 경우 integration event를 사용합니다. Consumer가 use case를 완료하기 위해 즉시 응답해야 한다면 explicit synchronous integration을 선호하고 availability, timeout, fallback을 contract의 일부로 다룹니다.

Localization과 culture

Culture가 선택되면 Lino는 message, validation, label, error, UI text를 처음부터 localize할 수 있도록 resource를 생성합니다. 이는 string이 endpoint, handler, component 전체에 이미 퍼진 뒤 internationalization을 늦은 patch로 처리하는 상황을 피하게 합니다.

Foundation 검증 후 service로 이동

생성 후 dependency를 restore하고 solution을 build한 다음 Aspire host를 실행합니다.

dotnet restore <ProjectName>.slnx
dotnet build <ProjectName>.slnx
dotnet run --project src/Aspire/AppHost/<ProjectName>.AppHost.csproj

이 단계에서 solution은 아직 foundation입니다. Editor에서 project를 열고 Aspire가 만든 resource를 review하며 cache, messaging, localization, code quality decision을 확인한 다음 application의 business capability를 나타낼 service로 진행합니다.

Service 생성 및 관리

Service는 runtime 및 ownership boundary입니다. Lino에서 service는 distributed system의 independent API를 나타낼 수도 있고, 점진적으로 evolve할 수 있는 solution 내부의 더 큰 business area를 나타낼 수도 있습니다.

Project foundation을 만든 뒤 다음 command로 service를 추가합니다.

lino service new

Service wizard는 다음 항목을 요청합니다.

  • Service namespace: folder, project, namespace에서 사용하는 technical name입니다.
  • Display name: service의 친숙한 이름입니다.
  • Service type: simplemodular 중 선택합니다.
  • Database provider: service database에 사용할 SQL Server 또는 PostgreSQL을 선택합니다.
  • Architecture style: 현재 simple service에는 Clean Architecture를 사용합니다.
  • Strongly Typed IDs: simple service에서 identifier를 dedicated type으로 생성할지 정의합니다.

Service type

Simple service: 명확한 boundary를 가진 business capability를 위한 더 직접적인 structure입니다. Focused API, microservice 또는 하나의 unit으로 evolve하기에 충분히 작은 area에 적합합니다.

Modular service: 더 큰 system, modular monolith 또는 여러 independent capability를 호스팅해야 하는 runtime에 적합한 structure입니다. Module은 organization과 model scalability를 높이지만 더 많은 dependency discipline을 요구합니다.

Type과 관계없이 각 service는 자체 database를 소유합니다. Simple service에서는 Strongly Typed IDs 결정이 service level에서 이루어지고, modular service에서는 module별로 이루어집니다.

Simple service

Simple service는 layer를 src/Services/<ServiceName> 바로 아래에 둡니다. Business capability가 명확한 boundary를 가지고 같은 runtime 내부에 여러 isolated module이 필요하지 않을 때 이 structure를 사용합니다. Focused API, microservice 또는 하나의 unit으로 evolve하기에 충분히 작은 domain area에 잘 맞습니다.

src/Services/<ServiceName>/
├── Domain/
│   └── <ProjectName>.<ServiceName>.Domain.csproj
├── Application/
│   └── <ProjectName>.<ServiceName>.Application.csproj
├── Infrastructure.Persistence/
│   └── <ProjectName>.<ServiceName>.Infrastructure.Persistence.csproj
├── Infrastructure/
│   └── <ProjectName>.<ServiceName>.Infrastructure.csproj
├── Api/
│   └── <ProjectName>.<ServiceName>.Api.csproj
├── Integration.Events/              (when messaging exists)
│   └── <ProjectName>.<ServiceName>.Integration.Events.csproj
├── Api.Contracts/                   (when typed HTTP consumption exists)
│   └── <ProjectName>.<ServiceName>.Api.Contracts.csproj
└── Api.Client/                      (when typed HTTP consumption exists)
    └── <ProjectName>.<ServiceName>.Api.Client.csproj
tests/Services/<ServiceName>/
├── UnitTests/
│   ├── Domain/
│   │   └── <ProjectName>.<ServiceName>.Domain.UnitTests.csproj
│   └── Application/
│       └── <ProjectName>.<ServiceName>.Application.UnitTests.csproj
└── IntegrationTests/
    └── <ProjectName>.<ServiceName>.IntegrationTests.csproj

Layer는 서로 다른 책임을 가집니다. Domain은 business rule과 invariant를 보호하고, Application은 use case를 orchestrate하며, Infrastructure.Persistence는 Entity Framework Core, repository, Unit of Work, migration을 포함합니다. Infrastructure는 technical integration을 포함하고, Api는 HTTP를 use case에 맞게 adapt합니다.

Api.ContractsApi.Client project는 typed HTTP consumption이 있을 때, 특히 Blazor Web App이 있는 solution에서 나타납니다. 첫 번째 project는 request, response, DTO, public type, shared client interface를 모으고, 두 번째 project는 HttpClient를 사용해 이 interface의 HTTP implementation을 제공하여 Blazor가 contract를 중복하지 않고 생성된 API를 소비하게 합니다.

Integration.Events project는 solution에 messaging이 활성화되어 있을 때 나타납니다. 일반적으로 messaging 및 outbox infrastructure와 함께, 다른 module, service 또는 system이 publish하고 consume하는 integration event를 포함합니다.

Modular service

Modular service는 modular monolith 또는 여러 bounded context를 호스팅하는 runtime에 적합합니다. Service 자체는 host, common infrastructure, database provider를 가지며 business rule은 module 내부에 있습니다.

src/Services/<ServiceName>/
├── Host/
│   └── <ProjectName>.<ServiceName>.Host.csproj
├── Infrastructure/
│   └── <ProjectName>.<ServiceName>.Infrastructure.csproj
└── Modules/
tests/Services/<ServiceName>/
└── Modules/

Host는 해당 service의 module, setting, endpoint, shared infrastructure를 compose합니다. Business rule을 포함해서는 안 됩니다. Service level의 Infrastructure project는 module이 공유하는 technical composition support를 제공합니다.

Module이 추가되면 Domain, Application, Infrastructure.Persistence, Infrastructure, Api project가 Modules/<ModuleName> 내부에 나타나 internal boundary를 보존합니다. Api.Contracts, Api.Client, Integration.Events project도 같은 조건을 따릅니다. Blazor/API client를 위한 typed HTTP consumption과 integration event를 위한 messaging 활성화가 그 조건입니다.

Database ownership

각 service는 자체 database를 소유합니다. 여러 service가 있는 solution에서는 한 service가 PostgreSQL을 사용하는 동안 다른 service는 SQL Server를 사용할 수 있습니다. 이 결정은 domain need, performance, operation, team maturity 또는 existing infrastructure와의 integration에 따라 달라질 수 있습니다.

Modular service에서 database는 runtime으로서 service에 속하지만 module은 schema, persistence project, 자체 migration으로 isolated됩니다. Table이 같은 physical database 안에 있더라도 module은 다른 module의 table을 직접 query해서는 안 됩니다.

Simple과 modular 중 선택

Boundary를 보호하는 가장 작은 structure를 선택합니다. 단일 domain boundary만 있다면 simple service로 충분합니다. 여러 capability가 runtime, deploy 또는 transaction을 공유해야 하지만 model, persistence, API, test를 계속 분리해야 한다면 modular service가 적합합니다.

선택사용 시점감수하는 비용
Simple serviceFocused domain, 적은 internal boundary, isolated API 또는 직접적인 microservice.초기 structure는 적지만 domain이 크게 성장하면 internal isolation이 부족할 수 있습니다.
Modular service같은 runtime의 multiple subdomain, modular monolith, independent area가 있는 SaaS 또는 module별 schema가 필요한 경우.더 많은 project, 더 많은 dependency discipline, internal contract에 대한 더 큰 주의가 필요합니다.

Architecture style

Lino가 생성한 service는 business rule을 technical detail에서 분리하기 위해 Clean Architecture를 따릅니다. Domain layer는 HTTP, Entity Framework Core, messaging, UI 또는 external provider를 알 필요가 없습니다. 이런 concern은 application의 edge에 둡니다.

  • Decoupling: core rule은 framework나 delivery mechanism에 의존하지 않습니다.
  • Maintainability: infrastructure change는 business rule에서 isolated되는 경향이 있습니다.
  • Testability: use case와 domain code를 더 적은 external dependency로 test할 수 있습니다.
  • Evolution: technical detail을 service core에 더 적은 영향으로 교체할 수 있습니다.

Clean Architecture와 Strongly Typed IDs

생성된 service는 business code가 HTTP, EF Core, messaging, UI 또는 infrastructure detail에 의존하지 않도록 Clean Architecture를 따릅니다. 이 분리는 test를 더 직접적으로 만들고 coupling을 줄이며 core rule을 다시 작성하지 않고 technical detail을 교체할 수 있게 합니다.

Strongly Typed IDs는 identifier가 실수로 섞이는 일을 막아 safety를 높입니다. 어떤 Guid, long, int든 받는 대신 domain은 각 entity 또는 aggregate마다 고유한 type을 사용할 수 있으며, 일반적으로 <EntityName>Id 형식입니다. 이를 통해 다른 entity가 예상되는 위치에 한 entity의 identifier를 전달하는 일을 막고 command, query, entity, handler, mapping의 signature를 더 expressive하게 만듭니다.

  • Type safety: 서로 다른 entity의 identifier를 실수로 섞는 일을 방지합니다.
  • Clarity: signature와 contract가 느슨한 primitive type보다 더 expressive해집니다.
  • Refactoring: format 또는 serialization change를 해당 ID type에 집중시킵니다.
  • Error reduction: 가능한 경우 잘못된 사용을 compile time에 드러냅니다.

Service 간 integration

한 service가 다른 service에 반응해야 할 때는 integration event, explicit HTTP integration 또는 의식적으로 replicated data를 선호합니다. 다른 service database에 direct access하면 structural coupling이 생기고 migration이 어려워지며 independent deploy가 더 위험해집니다.

Modular service의 다음 단계

Modular service를 만든 뒤 다음 단계는 lino module new로 module을 추가하는 것입니다. 각 module은 domain, application, persistence, API, test, integration과 함께 service의 internal boundary를 보존하면서 자체 business capability를 나타내야 합니다.

Module 생성 및 관리

Module은 modular service 내부에만 존재합니다. Module은 같은 runtime 안의 비즈니스 boundary를 나타내며, 자체 domain model, use case, persistence, API surface, integration event, test, database schema를 가집니다.

Service가 entity, rule, migration, contract를 섞지 않고 둘 이상의 business capability를 호스팅해야 할 때 module을 사용합니다. Modular monolith에서는 모든 것이 같은 process에서 실행될 수 있지만 내부 boundary는 여전히 중요합니다. 실제 isolation은 단순한 폴더 분리가 아니라 dependency control입니다.

lino module new --service <ServiceName>

생성 중 wizard는 다음 항목을 요청합니다.

  • Service: module을 호스팅할 modular service입니다. Simple service에는 내부 isolation에 필요한 구조가 없기 때문에 Lino는 simple service 내부에 module을 생성하도록 허용하지 않습니다.
  • Module namespace: folder, namespace, assembly, project에서 사용하는 기술적 이름입니다.
  • Display name: 사람이 읽는 위치에서 사용하는 module의 친숙한 이름입니다.
  • Strongly Typed IDs: module 내부에서 생성되는 identifier가 전용 type을 사용할지 정의합니다. 일반적으로 <EntityName>Id 형식입니다.

확인 후 Lino는 아키텍처 boundary를 무너뜨리지 않고 service 내부에 module을 추가합니다.

src/Services/<ServiceName>/
├── Host/
├── Infrastructure/
└── Modules/
    └── <ModuleName>/
        ├── Domain/
        │   └── <ProjectName>.<ServiceName>.<ModuleName>.Domain.csproj
        ├── Application/
        │   └── <ProjectName>.<ServiceName>.<ModuleName>.Application.csproj
        ├── Infrastructure.Persistence/
        │   └── <ProjectName>.<ServiceName>.<ModuleName>.Infrastructure.Persistence.csproj
        ├── Infrastructure/
        │   └── <ProjectName>.<ServiceName>.<ModuleName>.Infrastructure.csproj
        ├── Api/
        │   └── <ProjectName>.<ServiceName>.<ModuleName>.Api.csproj
        ├── Integration.Events/              (when messaging exists)
        │   └── <ProjectName>.<ServiceName>.<ModuleName>.Integration.Events.csproj
        ├── Api.Contracts/                   (when typed HTTP consumption exists)
        │   └── <ProjectName>.<ServiceName>.<ModuleName>.Api.Contracts.csproj
        └── Api.Client/                      (when typed HTTP consumption exists)
            └── <ProjectName>.<ServiceName>.<ModuleName>.Api.Client.csproj
tests/Services/<ServiceName>/Modules/<ModuleName>/
├── UnitTests/
│   ├── Domain/
│   │   └── <ProjectName>.<ServiceName>.<ModuleName>.Domain.UnitTests.csproj
│   └── Application/
│       └── <ProjectName>.<ServiceName>.<ModuleName>.Application.UnitTests.csproj
└── IntegrationTests/
    └── <ProjectName>.<ServiceName>.<ModuleName>.IntegrationTests.csproj

생성된 artifact의 책임

ArtifactModule 내부의 책임
DomainEntity, aggregate, Value Objects, enumeration, domain event, repository contract, module invariant입니다.
ApplicationUse case, command, query, handler, validation, 내부 input/output contract, module rule orchestration입니다.
Infrastructure.PersistenceDbContext, Entity Framework Core configuration, concrete repository, Unit of Work, module migration입니다.
InfrastructureModule에 특화된 기술 구현, adapter, provider, dependency composition입니다.
ApiHTTP endpoint, versioning, filter, authorization, 외부 request와 use case 사이의 adaptation입니다.
Api.ContractsTyped HTTP consumption이 있을 때, 보통 Blazor Web App에 의해 생성됩니다. API와 consumer 사이에 공유되는 request, response, DTO, public type, client interface를 포함합니다.
Api.ClientTyped HTTP consumption이 있을 때 contract와 함께 생성됩니다. HttpClient, provider, option, helper를 사용한 interface의 HTTP 구현을 포함하여 Blazor project가 생성된 API를 일관되고 strong typed 방식으로 소비하게 합니다.
Integration.EventsProject에 messaging이 있을 때 생성됩니다. 다른 module, service, system이 publish하고 consume하는 integration event를 포함하며 payload를 명시적 contract로 유지합니다.

Database structure

Database는 개별 module이 아니라 service에 계속 연결됩니다. Modular service 내부에서 각 module은 연결된 database 안에서 자체 schema로 표현되며 자체 persistence project와 migration을 가집니다. 이를 통해 module마다 여러 physical database를 요구하지 않고도 isolation과 organization을 제공합니다.

Module isolation과 independence

Module은 다른 module의 DbContext, entity, repository 또는 internal service에 직접 접근해서는 안 됩니다. 각 module은 자체 model과 자체 persistence를 가집니다. 다른 module이 data를 필요로 할 때는 내부 구현 detail을 통해 boundary를 넘지 말고 explicit integration을 사용합니다.

이렇게 하면 겉으로는 local change처럼 보이는 변경이 system의 다른 영역을 깨뜨리는 일을 막을 수 있습니다. Consumer module이 다른 module에 속한 data를 조회해야 한다면 source module의 전체 entity에 의존해서는 안 됩니다. 자체 use case에 필요한 최소 data를 가진 shadow entity를 유지하고, integration 또는 event로 채울 수 있습니다.

이 decoupling의 장점:

  • Isolation: 각 module은 다른 module의 internal detail을 넘지 않고 rule, persistence, test를 발전시킬 수 있습니다.
  • Organization: Application은 bounded context를 존중하고 ownership을 명확히 드러냅니다.
  • Flexibility: Module을 service의 나머지 부분에 더 적은 영향으로 추가, 제거 또는 refactor할 수 있습니다.
  • Testing ease: 각 module을 더 독립적으로 검증할 수 있어 변경에 대한 신뢰가 높아집니다.

Module 간 communication

Communication에는 explicit contract를 사용합니다. 같은 runtime 내부 호출에는 internal integration이 생성된 경우 Integration.Contracts에 contract를 노출하고 in-process implementation을 사용할 수 있습니다. Runtime 간 호출에는 HTTP integration을 사용합니다. Asynchronous propagation에는 Integration.Events에 integration event를 publish하고, publish가 transactional operation의 일부라면 messaging/outbox infrastructure를 사용합니다.

Schema, migration, database

Modular service에서 module은 service의 database provider를 공유하지만, 각 module은 자체 schema와 자체 Infrastructure.Persistence project로 표현됩니다. Migration은 module별로 생성되어 database evolution을 business boundary에 맞춥니다.

이 분리는 각 module이 table, seed, index, foreign key를 발전시키면서도 service database가 모두가 공유하는 단일 model로 변하는 것을 막습니다. Schema는 module이 같은 process에서 실행되고 같은 physical database를 사용하더라도 business boundary를 강화하는 technical boundary입니다.

좋은 module boundary

Technical layer가 아니라 business capability를 중심으로 module을 만듭니다. 좋은 module name은 area, process 또는 자체 rule을 가진 capability처럼 domain에서 인식할 수 있는 책임을 설명해야 합니다. Business rule에는 Common, Core, Utilities 같은 generic name을 피합니다. 이런 이름은 ownership을 숨기고 지나치게 공유되는 dependency가 되기 쉽습니다.

  • Cohesion: module은 함께 변경되는 language, rule, data를 가져야 합니다.
  • Autonomy: 다른 module의 internal entity에 접근하지 않고 module을 test하고 evolve할 수 있어야 합니다.
  • Clear contracts: module 외부에 필요한 data는 direct database access가 아니라 API, integration, event 또는 shadow entity로 노출해야 합니다.
  • Low coupling: 두 module이 항상 같은 entity를 변경해야 한다면 boundary를 재검토해야 할 가능성이 높습니다.

Module이 생성되면 다음 documentation topic에서 이 boundary 안에서 entity, Value Objects, enumeration, command, query, API, event, integration, migration을 model하는 방법을 보여줍니다.

처리되지 않은 오류가 발생했습니다. 새로 고침 🗙