이벤트 다루기

ํ˜„๋Œ€ ์‹œ์Šคํ…œ์—์„œ DDD(Domain-Driven Design) ๋ฐ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜(Clean Architecture)์™€ ๊ฐ™์€ ์•„ํ‚คํ…์ฒ˜ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋”ฐ๋ฅด๋Š” ๊ฒฝ์šฐ, ์ด๋ฒคํŠธ๋Š” ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋ชจ๋ธ๋งํ•˜๊ณ  ๊ตฌ์„ฑ ์š”์†Œ ๋˜๋Š” ์‹œ์Šคํ…œ ๊ฐ„ ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ํ•„์ˆ˜์ ์ธ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋„๋ฉ”์ธ์—์„œ ์ด๋ฏธ ๋ฐœ์ƒํ•œ ์ผ์ด๋ฉฐ, ์‹œ์Šคํ…œ์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด๋‚˜ ์™ธ๋ถ€ ์„œ๋น„์Šค์—์„œ ๊ด€์‹ฌ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณด๊ฐ•: This section also covers shadow entities and the Transactional Outbox flow with DDD, MediatR, MassTransit, RabbitMQ, Hangfire, and Unit of Work for reliable messaging.

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ๋‚ด์—์„œ ๋ฐœ์ƒํ•œ ์ค‘์š”ํ•œ ์‚ฌ์‹ค์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์ด ์ด๋ฒคํŠธ๋Š” ์‹œ์Šคํ…œ ๋‚ด๋ถ€์—์„œ ์ƒ์„ฑ ๋ฐ ์†Œ๋น„๋˜๋ฉฐ, ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋А์Šจํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋œ ๋ฐฉ์‹์œผ๋กœ ์ „ํŒŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๊ฐ์ฒด๋“ค์ด ์„œ๋กœ๋ฅผ ์ง์ ‘ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ์–ธ์ œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋‚˜์š”?

  • ์–ด๋–ค ์ž‘์—…์ด ๋„๋ฉ”์ธ์— ์ค‘์š”ํ•œ ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๊ณ , ์‹œ์Šคํ…œ์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์— ์•Œ๋ฆผ ๋˜๋Š” ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ํ•„์š”ํ•  ๋•Œ.
  • ๋„๋ฉ”์ธ ๋ชจ๋ธ์˜ ์‘์ง‘๋ ฅ์„ ์œ ์ง€ํ•˜๊ณ , ์„œ๋กœ ์ง์ ‘ ์˜์กดํ•˜์ง€ ์•Š๊ณ ๋„ ๋‹ค์–‘ํ•œ ํ”„๋กœ์„ธ์Šค๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•  ๋•Œ.

Lino์—์„œ๋Š” ์ƒˆ๋กœ์šด ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์„ธ์š”:

lino event new
lino event new --name <EventName> --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino event edit --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino event list --service <ServiceName> --module <ModuleName> --entity <EntityName>

CLI ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ๋‹ค์Œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค:

  • ์„œ๋น„์Šค โ€“ ์ด๋ฒคํŠธ๊ฐ€ ์ƒ์„ฑ๋  ์„œ๋น„์Šค.
  • ๋ชจ๋“ˆ โ€“ ์ด๋ฒคํŠธ๊ฐ€ ์ƒ์„ฑ๋  ๋ชจ๋“ˆ(๋ชจ๋“ˆํ˜• ์„œ๋น„์Šค๋งŒ ํ•ด๋‹น).
  • ์—”ํ‹ฐํ‹ฐ โ€“ ์ด๋ฒคํŠธ๊ฐ€ ์ƒ์„ฑ/์—ฐ๊ฒฐ๋  ์—”ํ‹ฐํ‹ฐ.
  • ์ด๋ฒคํŠธ ์œ ํ˜• โ€“ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋˜๋Š” ํ†ตํ•ฉ ์ด๋ฒคํŠธ.
  • ์ด๋ฒคํŠธ ์ด๋ฆ„ โ€“ ๋„๋ฉ”์ธ์—์„œ ์‚ฌ์šฉ๋˜๋ฉฐ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ฒฐ๋˜๋Š” ์ด๋ฆ„.

์˜ˆ์‹œ

User ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ฒฐ๋œ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ UserCreated๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ์ž๋™์œผ๋กœ UserCreatedDomainEvent๋ผ๋Š” ์ด๋ฆ„์˜ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ์ด๋ฆ„์€ ์ด๋ฒคํŠธ๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์‹œ์Šคํ…œ์˜ ์–ด๋А ๋ถ€๋ถ„์—์„œ๋„ ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์ž‘์—…์ด ์ด๋ฏธ ์™„๋ฃŒ๋˜์—ˆ์Œ์„ ๋ช…ํ™•ํžˆ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

Lino๊ฐ€ ์ƒ์„ฑํ•œ ๊ตฌ์กฐ:

<ProjectName>/
โ””โ”€โ”€ src/
    โ””โ”€โ”€ Services/
        โ””โ”€โ”€ <ServiceName>/
            โ””โ”€โ”€ Domain/
                โ”œโ”€โ”€ <ProjectName>.<ServiceName>.Domain.csproj
                โ””โ”€โ”€ Aggregates/
                    โ””โ”€โ”€ Users/
                        โ”œโ”€โ”€ User.cs
                        โ”œโ”€โ”€ Errors/
                        โ”œโ”€โ”€ Events/
                        โ”‚   โ””โ”€โ”€ UserCreatedDomainEvent.cs
                        โ”œโ”€โ”€ Repositories/
                        โ””โ”€โ”€ Resources/

Technical flow in Lino

  1. The aggregate registers an IDomainEvent with RegisterDomainEvent.
  2. The command handler saves through IUnitOfWork, preferably SaveChangesInTransactionAsync when events are involved.
  3. The Unit of Work persists changes and publishes events through MediatR.
  4. A domain handler can register an integration event in IOutbox.

Events are not commands; use past-tense names such as UserCreatedDomainEvent.

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ

Domain Event Handler๋Š” ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด๋ถ€ ์ƒํƒœ์™€ ๊ด€๋ จ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํด๋ž˜์Šค์ด๋ฉฐ, ํ•ญ์ƒ ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ ๋‚ด์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ํ•ธ๋“ค๋Ÿฌ์˜ ์ฃผ์š” ๋ชฉ์ ์€ ์‹œ์Šคํ…œ์„ ์‘์ง‘๋ ฅ ์žˆ๊ณ  ๋А์Šจํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜์—ฌ ์—”ํ‹ฐํ‹ฐ๋‚˜ ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ์˜ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ณผ๋ถ€ํ•˜ํ•˜์ง€ ์•Š๊ณ  ์ถ”๊ฐ€ ๊ทœ์น™์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด User๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ํ†ต๊ณ„๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ ๋‚ด๋ถ€ ๋กœ๊ทธ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ์— ์•Œ๋ฆผ์„ ๋ณด๋‚ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ž‘์—…์€ ๋„๋ฉ”์ธ ๋‚ด์—์„œ ์˜๋ฏธ๊ฐ€ ์žˆ์œผ๋ฉฐ ๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰๋  ์ˆ˜ ์žˆ์–ด ์ฆ‰๊ฐ์ ์ธ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค์— ์˜์กดํ•˜๋Š” ์ž‘์—… โ€” ์ด๋ฉ”์ผ ์ „์†ก ๋˜๋Š” ์„œ๋“œํŒŒํ‹ฐ API ํ˜ธ์ถœ ๋“ฑ โ€” ์€ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ์—์„œ ์ง์ ‘ ์ˆ˜ํ–‰ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ๋А๋ฆฌ๊ฑฐ๋‚˜ ๋ถˆ์•ˆ์ •ํ•œ ์ž‘์—…์— ๋ฌถ์ด๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๋„๋ฉ”์ธ์€ ํ†ตํ•ฉ ์ด๋ฒคํŠธ(Outbox์— ๊ธฐ๋ก)๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋‚˜์ค‘์— ๋น„๋™๊ธฐ์ ์ด๊ณ  ํšŒ๋ณต๋ ฅ ์žˆ๊ฒŒ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ํŠน์ง•:

  • ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•˜์ง€๋งŒ ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋„๋ฉ”์ธ ์ผ๊ด€์„ฑ๊ณผ ๊ด€๋ จ๋œ ์ธํ”„๋กœ์„ธ์Šค ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ชจ๋“  ์ž‘์—…์ด ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

์ƒˆ๋กœ์šด ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”:

lino event-handler new

CLI ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ๋‹ค์Œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค:

  • ์„œ๋น„์Šค โ€“ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ƒ์„ฑ๋  ์„œ๋น„์Šค.
  • ๋ชจ๋“ˆ โ€“ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ƒ์„ฑ๋  ๋ชจ๋“ˆ(๋ชจ๋“ˆํ˜• ์„œ๋น„์Šค์—์„œ๋งŒ ํ•ด๋‹น).
  • ์—”ํ‹ฐํ‹ฐ โ€“ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ƒ์„ฑ๋  ์—”ํ‹ฐํ‹ฐ.
  • ์ด๋ฒคํŠธ ์œ ํ˜• โ€“ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋˜๋Š” ํ†ตํ•ฉ ์ด๋ฒคํŠธ.
  • ์ด๋ฒคํŠธ โ€“ ์†Œ๋น„๋  ์ด๋ฒคํŠธ.
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ด๋ฆ„ โ€“ ์—”ํ‹ฐํ‹ฐ ๋ฐ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ์— ์—ฐ๊ฒฐ๋  ์ด๋ฆ„.

์˜ˆ์‹œ

User ์—”ํ‹ฐํ‹ฐ์™€ UserCreatedDomainEvent ์ด๋ฒคํŠธ์— ์—ฐ๊ฒฐ๋œ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ UserCreated๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ์ž๋™์œผ๋กœ UserCreatedDomainEventHandler๋ผ๋Š” ์ด๋ฆ„์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Lino๊ฐ€ ์ƒ์„ฑํ•œ ๊ตฌ์กฐ:

<ProjectName>/
โ””โ”€โ”€ src/
    โ””โ”€โ”€ Services/
        โ””โ”€โ”€ <ServiceName>/
            โ””โ”€โ”€ Application/
                โ”œโ”€โ”€ <ProjectName>.<ServiceName>.Application.csproj
                โ””โ”€โ”€ UseCases/
                    โ””โ”€โ”€ Users/
                        โ”œโ”€โ”€ Commands/
                        โ”œโ”€โ”€ EventHandlers/
                        โ”‚   โ””โ”€โ”€ Domain/
                        โ”‚       โ””โ”€โ”€ UserCreatedDomainEventHandler.cs
                        โ”œโ”€โ”€ Logging/
                        โ”œโ”€โ”€ Queries/
                        โ””โ”€โ”€ Resources/

Technical responsibilities

  • Implements IDomainEventHandler and reacts inside the application boundary.
  • Runs internal consistency, logging, tracing, or integration registration work.
  • Uses IOutbox.RegisterIntegrationEvent for reliable messages.
  • Keeps the business change and OutboxMessage in the same transaction.

ํ†ตํ•ฉ ์ด๋ฒคํŠธ

ํ†ตํ•ฉ ์ด๋ฒคํŠธ๋Š” ์ค‘์š”ํ•œ ์ผ์ด ๋ฐœ์ƒํ–ˆ์Œ์„ ์•Œ๋ฆฌ๋Š” ๋ฉ”์‹œ์ง€์ด๋ฉฐ, ์™ธ๋ถ€ ์‹œ์Šคํ…œ์ด๋‚˜ ๋‹ค๋ฅธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์™€ ๊ณต์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ์™€ ๋‹ฌ๋ฆฌ, ์—ฌ๊ธฐ์„œ์˜ ๋ชฉ์ ์€ ์‹œ์Šคํ…œ ๊ฐ„ ํ†ต์‹  ๋ฐ ์ƒํƒœ ๋™๊ธฐํ™”์ž…๋‹ˆ๋‹ค.

ํ†ตํ•ฉ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ:

  • ์‹œ์Šคํ…œ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์— ๋ฐ˜์˜ํ•ด์•ผ ํ•  ๋•Œ.
  • ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๊ฐ€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ฒŒ์‹œํ•˜์—ฌ ๋‹ค๋ฅธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๊ฐ€ ๋ฐ˜์‘ํ•ด์•ผ ํ•  ๋•Œ.

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ์™€ ํ†ตํ•ฉ ์ด๋ฒคํŠธ์˜ ์ฃผ์š” ์ฐจ์ด์ :

ํ•ญ๋ชฉ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ํ†ตํ•ฉ ์ด๋ฒคํŠธ
๋Œ€์ƒ ๋‚ด๋ถ€ ์™ธ๋ถ€
๊ฒฐํ•ฉ๋„ ๋‚ฎ์Œ (๋‚ด๋ถ€) ํ•„์š” (์‹œ์Šคํ…œ ๊ฐ„)
์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ฆ‰์‹œ ๋น„๋™๊ธฐ์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ „๋‹ฌ ๋ณด์žฅ ํฌํ•จ
์˜์†์„ฑ ํ•„์š” ์—ฌ๋ถ€ ํ•„์ˆ˜ ์•„๋‹˜ ์˜ˆ (์‹ ๋ขฐ์„ฑ๊ณผ ๋ณต์›๋ ฅ ํ™•๋ณด๋ฅผ ์œ„ํ•ด)

Lino์—์„œ๋Š” ์ƒˆ ํ†ตํ•ฉ ์ด๋ฒคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”:

lino event new
lino event new --name <EventName> --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino event list --service <ServiceName> --module <ModuleName> --entity <EntityName>

CLI ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ์š”์ฒญํ•˜๋Š” ํ•ญ๋ชฉ:

  • ์„œ๋น„์Šค โ€“ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•  ์„œ๋น„์Šค.
  • ๋ชจ๋“ˆ โ€“ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•  ๋ชจ๋“ˆ (๋ชจ๋“ˆํ˜• ์„œ๋น„์Šค์—์„œ๋งŒ).
  • ์—”ํ‹ฐํ‹ฐ โ€“ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑ/์—ฐ๊ฒฐํ•  ์—”ํ‹ฐํ‹ฐ.
  • ์ด๋ฒคํŠธ ์œ ํ˜• โ€“ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋˜๋Š” ํ†ตํ•ฉ ์ด๋ฒคํŠธ.
  • ์ด๋ฒคํŠธ ์ด๋ฆ„ โ€“ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ฒฐ๋œ ํ†ตํ•ฉ ์ด๋ฒคํŠธ ์ด๋ฆ„.

์˜ˆ์‹œ

User ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ฒฐ๋œ ํ†ตํ•ฉ ์ด๋ฒคํŠธ UserCreated๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ์ž๋™์œผ๋กœ UserCreatedIntegrationEvent๋ผ๋Š” ์ด๋ฆ„์˜ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ์ด๋ฆ„์€ ์ด๋ฒคํŠธ๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์‹œ์Šคํ…œ์˜ ๋ชจ๋“  ๋ถ€๋ถ„์—์„œ ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์ž‘์—…์ด ์ด๋ฏธ ์™„๋ฃŒ๋˜์—ˆ์Œ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

Lino๊ฐ€ ์ƒ์„ฑํ•œ ๊ตฌ์กฐ:

<ProjectName>/
โ””โ”€โ”€ src/
    โ””โ”€โ”€ Services/
        โ””โ”€โ”€ <ServiceName>/
            โ””โ”€โ”€ Integration.Events/
                โ”œโ”€โ”€ <ProjectName>.<ServiceName>.Integration.Events.csproj
                โ””โ”€โ”€ Users/
                    โ””โ”€โ”€ UserCreatedIntegrationEvent.cs

Reliable publication

An integration event implements IIntegrationEvent and crosses boundaries between modules, services, or systems. The application registers the event in IOutbox, the Unit of Work persists an OutboxMessage, and a Hangfire worker later publishes it through MassTransit/RabbitMQ.

ํ†ตํ•ฉ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ

Integration Event Handler ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค๋ฅธ ์„œ๋น„์Šค๋‚˜ ์ปจํ…์ŠคํŠธ์—์„œ ๋ฐœํ–‰๋œ Integration Event ๋ฅผ ์†Œ๋น„ํ•˜๊ณ , ์ž์‹ ์˜ ๋„๋ฉ”์ธ ๋‚ด์—์„œ ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ํ•ธ๋“ค๋Ÿฌ๋Š” RabbitMQ, Kafka, Azure Service Bus ๋“ฑ๊ณผ ๊ฐ™์€ ๋ฉ”์‹œ์ง• ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ Outbox ํŒจํ„ด๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์–ด ์•ˆ์ •์ ์ธ ์ „๋‹ฌ๊ณผ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, Identity ์„œ๋น„์Šค์—์„œ UserCreated ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋ฉด, ๋‹ค๋ฅธ ์ปจํ…์ŠคํŠธ์˜ ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ๋Š” ํ™˜์˜ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๊ฑฐ๋‚˜ ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋“ฑ์˜ ๋ฐ˜์‘์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ž‘์—…์€ ๋А๋ฆฌ๊ฑฐ๋‚˜ ์‹คํŒจํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€์—์„œ ์ฒ˜๋ฆฌ๋˜๋ฏ€๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‚ด๋ถ€ ์ผ๊ด€์„ฑ์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ฃผ์š” ํŠน์ง•:

  • ๋‹ค๋ฅธ ์ปจํ…์ŠคํŠธ์™€ ๊ด€๋ จ๋œ ๋น„์ฆˆ๋‹ˆ์Šค ์‚ฌ์‹ค์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•ฉ๋‹ˆ๋‹ค.
  • ๋А๋ฆฌ๊ฑฐ๋‚˜ ์™ธ๋ถ€ ์ž‘์—…(์˜ˆ: ์ด๋ฉ”์ผ ๋ฐœ์†ก, API ํ˜ธ์ถœ)์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ๋น„๋™๊ธฐ์ ์ด๊ณ  ๋ณต์›๋ ฅ ์žˆ๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์ข…์ข… ์žฌ์‹œ๋„ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
  • ์™ธ๋ถ€ ์‹คํŒจ๊ฐ€ ์›๋ž˜ ๋„๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ ๋ฐ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ๊ฐ„์˜ ํ†ตํ•ฉ์„ ์šฉ์ดํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

์ƒˆ ํ†ตํ•ฉ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์„ธ์š”:

lino event-handler new
lino event-handler new --name <EventHandlerName> --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino event-handler list --service <ServiceName> --module <ModuleName> --entity <EntityName>

CLI ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ์š”์ฒญํ•˜๋Š” ํ•ญ๋ชฉ:

  • ์„œ๋น„์Šค โ€“ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ƒ์„ฑ๋  ์„œ๋น„์Šค.
  • ๋ชจ๋“ˆ โ€“ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ƒ์„ฑ๋  ๋ชจ๋“ˆ(๋ชจ๋“ˆํ˜• ์„œ๋น„์Šค๋งŒ ํ•ด๋‹น).
  • ์—”ํ‹ฐํ‹ฐ โ€“ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ƒ์„ฑ๋  ์—”ํ‹ฐํ‹ฐ.
  • ์ด๋ฒคํŠธ ์œ ํ˜• โ€“ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋˜๋Š” ํ†ตํ•ฉ ์ด๋ฒคํŠธ.
  • ์„œ๋น„์Šค โ€“ ์†Œ๋น„ํ•  ์ด๋ฒคํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š” ์„œ๋น„์Šค.
  • ๋ชจ๋“ˆ โ€“ ์†Œ๋น„ํ•  ์ด๋ฒคํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š” ๋ชจ๋“ˆ(๋ชจ๋“ˆํ˜• ์„œ๋น„์Šค๋งŒ ํ•ด๋‹น).
  • ์—”ํ‹ฐํ‹ฐ โ€“ ์†Œ๋น„ํ•  ์ด๋ฒคํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ.
  • ์ด๋ฒคํŠธ โ€“ ์†Œ๋น„ํ•  ์ด๋ฒคํŠธ.
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ด๋ฆ„ โ€“ ์—”ํ‹ฐํ‹ฐ ๋ฐ ํ†ตํ•ฉ ์ด๋ฒคํŠธ์™€ ์—ฐ๊ฒฐ๋  ์ด๋ฆ„.

์˜ˆ์ œ

User ์—”ํ‹ฐํ‹ฐ ๋ฐ UserCreatedIntegrationEvent ์ด๋ฒคํŠธ์™€ ์—ฐ๊ฒฐ๋œ ํ†ตํ•ฉ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ SendEmailOnUserCreated ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์‹œ์Šคํ…œ์€ ์ž๋™์œผ๋กœ SendEmailOnUserCreatedIntegrationEventHandler ๋ผ๋Š” ์ด๋ฆ„์˜ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Lino๊ฐ€ ์ƒ์„ฑํ•œ ๊ตฌ์กฐ:

<ProjectName>/
โ””โ”€โ”€ src/
    โ””โ”€โ”€ Services/
        โ””โ”€โ”€ <ServiceName>/
            โ””โ”€โ”€ Application/
                โ”œโ”€โ”€ <ProjectName>.<ServiceName>.Application.csproj
                โ””โ”€โ”€ UseCases/
                    โ””โ”€โ”€ Users/
                        โ”œโ”€โ”€ Commands/
                        โ”œโ”€โ”€ EventHandlers/
                        โ”‚   โ””โ”€โ”€ Integration/
                        โ”‚       โ””โ”€โ”€ SendEmailOnUserCreatedIntegrationEventHandler.cs
                        โ”œโ”€โ”€ Logging/
                        โ”œโ”€โ”€ Queries/
                        โ””โ”€โ”€ Resources/

Operational guidance

  • Implements IIntegrationEventHandler and consumes messages with MassTransit.
  • Does not read the producer database directly.
  • Updates local state, projections, or shadow entities.
  • Should be idempotent because retries and duplicate messages can happen.

Shadow Entities

shadow entity๋Š” ์†Œ์œ ์ž๊ฐ€ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ์ปฌ์— ์ตœ์†Œํ•œ์œผ๋กœ, ํ†ต์ œ๋œ ๋ฐฉ์‹์œผ๋กœ ๋ณต์‚ฌํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. consumer๊ฐ€ producer ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ง์ ‘ ์ ‘๊ทผํ•˜์ง€ ์•Š๊ณ  ์ฐธ์กฐ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒ, ๊ฒ€์ฆ ๋˜๋Š” ํ‘œ์‹œํ•ด์•ผ ํ•  ๋•Œ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Shadow Entities๋Š” eventual consistency๋ฅผ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ์„ ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. producer๊ฐ€ integration event๋ฅผ ๊ฒŒ์‹œํ•˜๊ณ , ๋ฉ”์‹œ์ง€๋Š” Outbox์— ์ €์žฅ๋˜๋ฉฐ, worker๊ฐ€ bus์— ๊ฒŒ์‹œํ•˜๊ณ , consumer๋Š” integration event handler๋ฅผ ํ†ตํ•ด ๋กœ์ปฌ ๋ณต์‚ฌ๋ณธ์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.

lino shadow-entity new
lino event new
lino event-handler new

์ด ๋ช…๋ น์€ lino shadow new alias๋กœ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€ํ™”ํ˜• ํ๋ฆ„์—์„œ Lino๋Š” ๋Œ€์ƒ ์„œ๋น„์Šค ๋˜๋Š” ๋ชจ๋“ˆ, ์›๋ณธ entity, ๋ณต์‚ฌํ•  properties๋ฅผ ๋ฌป์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด identifier type, ํ•„์ˆ˜ ์—ฌ๋ถ€, string ๊ธธ์ด, ๊ธฐ๋ณธ references๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋‹ค์‹œ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์ค„์–ด๋“ค๊ณ  source์™€ consumer ์‚ฌ์ด์˜ ๋ถˆ์ผ์น˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

shadow entity๋Š” ์ „์ฒด ๋ณต์ œ๋ณธ์ด ์•„๋‹™๋‹ˆ๋‹ค. Catalog ๋ชจ๋“ˆ์ด Tenancy์™€ Security์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์˜ˆ์—์„œ catalog๋Š” Tenant.Id, Tenant.Slug, User.Id, User.Email, User.TenantId๋งŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์›๋ž˜ user entity์—๋Š” hash, confirmation, tokens, dates ๋ฐ catalog context์— ์†ํ•˜์ง€ ์•Š๋Š” ๋‹ค๋ฅธ ๋‚ด๋ถ€ ํ•„๋“œ๊ฐ€ ๊ณ„์† ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉํ•  ๋•Œ

  • consumer๊ฐ€ ๋‹ค๋ฅธ context์— ์†ํ•œ entity์˜ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ํ•„์š”๋กœ ํ•  ๋•Œ.
  • ๊ทœ์น™์ด producer ๋ณ€๊ฒฝ๊ณผ consumer ์—…๋ฐ์ดํŠธ ์‚ฌ์ด์˜ ์ž‘์€ ์ง€์—ฐ์„ ํ—ˆ์šฉํ•  ๋•Œ.
  • producer ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ์ฝ๋Š” ๊ฒƒ์ด ๋ชจ๋“ˆ์ด๋‚˜ ์„œ๋น„์Šค ๊ฐ„์˜ ๋ถ€์ ์ ˆํ•œ ๊ฒฐํ•ฉ์„ ๋งŒ๋“ค ๋•Œ.
  • grids, filters, local validations ๋˜๋Š” projections๊ฐ€ ์™ธ๋ถ€ ์ฐธ์กฐ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„์š”๋กœ ํ•  ๋•Œ.

ํ”ผํ•ด์•ผ ํ•  ๋•Œ

  • ๊ทœ์น™์ด ์ฆ‰์‹œ ๊ฐฑ์‹ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”๊ตฌํ•˜๊ณ  eventual consistency๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ.
  • ๋กœ์ปฌ ๋ณต์‚ฌ๋ณธ์ด ์›๋ž˜ aggregate์˜ ์ „์ฒด ๋ณต์ œ๋ณธ์ด ๋  ๋•Œ.
  • consumer๊ฐ€ producer์— ์†ํ•œ invariants์— ์˜์กดํ•˜๊ธฐ ์‹œ์ž‘ํ•  ๋•Œ.

๊ถŒ์žฅ ํ๋ฆ„

  1. consumer์— ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ํฌํ•จํ•˜๋„๋ก shadow entity๋ฅผ ๋ชจ๋ธ๋งํ•ฉ๋‹ˆ๋‹ค.
  2. producer๊ฐ€ ๊ฒŒ์‹œํ•˜๋Š” integration event๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
  3. consumer์— integration event handler๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  4. producer key ๋˜๋Š” natural identifier๋ฅผ ์‚ฌ์šฉํ•ด idempotent upsert๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  5. retries, ์ค‘๋ณต ๋ฉ”์‹œ์ง€, ์˜๊ตฌ ์‹คํŒจ๋ฅผ ์ง„๋‹จํ•  ์ˆ˜ ์žˆ์„ ๋งŒํผ ์ถฉ๋ถ„ํ•œ logs๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋™๊ธฐํ™”

์ „์ฒด ํ๋ฆ„์€ domain, application, Unit of Work, Outbox, worker, bus, consumer๋ฅผ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค. ๋ชฉํ‘œ๋Š” ๋ชจ๋“ˆ์ด๋‚˜ ์„œ๋น„์Šค ๊ฐ„์˜ ์ทจ์•ฝํ•œ ์ง์ ‘ ํ˜ธ์ถœ์„ ํ”ผํ•˜๊ณ , ๊ฒŒ์‹œ ๋˜๋Š” ์†Œ๋น„๊ฐ€ ์‹คํŒจํ–ˆ์„ ๋•Œ retry, audit, reprocessing์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  1. aggregate๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ณ  domain event๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
  2. Unit of Work๊ฐ€ transaction ์•ˆ์—์„œ ๋ณ€๊ฒฝ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  3. domain handler๊ฐ€ ๊ทธ ์‚ฌ์‹ค์— ๋ฐ˜์‘ํ•˜์—ฌ integration event๋ฅผ IOutbox์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
  4. Unit of Work๊ฐ€ business data์™€ OutboxMessage๋ฅผ ๊ฐ™์€ transaction์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  5. Hangfire worker๊ฐ€ Outbox๋ฅผ ์ฝ๊ณ  MassTransit/RabbitMQ๋ฅผ ํ†ตํ•ด bus์— ๊ฒŒ์‹œํ•ฉ๋‹ˆ๋‹ค.
  6. consumer๊ฐ€ IIntegrationEventHandler๋ฅผ ํ†ตํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  7. consumer๋Š” local model, shadow entity, projection, email, external integration ๋˜๋Š” ๋‹ค๋ฅธ asynchronous flow๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.
Domain change -> Domain Event -> Domain Handler -> Integration Event -> Outbox -> Worker -> Event Bus -> Consumer -> Local model

์„ค๊ณ„ ๊ทœ์น™

  • invariants๋Š” domain์— ๋‘ก๋‹ˆ๋‹ค. events๋Š” ํ•„์ˆ˜ ๊ทœ์น™์„ ์ˆจ๊ธฐ๊ธฐ ์œ„ํ•ด์„œ๊ฐ€ ์•„๋‹ˆ๋ผ reactions๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • integration events๋Š” business change์™€ ๊ฐ™์€ transaction ์•ˆ์—์„œ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
  • integration contracts๋Š” ์ž‘๊ณ , versionableํ•˜๋ฉฐ, ์•ˆ์ •์ ์œผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • retries์™€ ์ค‘๋ณต ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ idempotent consumers๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • worker failures์™€ Outbox์— ๋ฉˆ์ถ˜ messages๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.

์‹ค์งˆ์ ์ธ ๊ฒฐ๊ณผ: producer๋Š” consumers๋ฅผ ์•Œ ํ•„์š”๊ฐ€ ์—†๊ณ , consumers๋Š” producer ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜์ง€ ์•Š์œผ๋ฉฐ, broker๋‚˜ consumer๊ฐ€ ์ผ์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด๋„ context ๊ฐ„ ํ†ต์‹ ์€ resilientํ•˜๊ฒŒ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

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