"매 두 system 의 model / protocol 차이를 매 어디선가 흡수해야 한다 — 매 그 자리를 well-chosen 한 곳으로". 매 EE 의 source-load impedance match 의 metaphor — software 에서 매 OO ↔ relational, REST ↔ event, sync ↔ async, monolith ↔ microservice 사이의 mismatch 를 anti-corruption layer / DTO / ORM / adapter 로 매 흡수.
매 핵심
매 classic mismatches
OO ↔ Relational: object identity vs row, inheritance vs table, association vs FK.
REST ↔ Event-driven: request/response vs publish/subscribe, sync vs async.
Internal model ↔ External API: domain entity vs DTO/contract.
Bounded context 간: 매 같은 단어 ("Order") 가 매 다른 의미.
매 흡수 위치
ORM (Prisma, Drizzle, SQLAlchemy, Hibernate): OR mismatch.
DTO / Schema (Zod, Pydantic, Protobuf): API boundary.
Anti-Corruption Layer (ACL): bounded context 간 (Evans DDD).
Adapter / Port (Hex / Clean architecture): 매 infra ↔ domain.
Event envelope / outbox: sync ↔ async.
매 응용
ORM choice / hand-rolled SQL trade-off.
GraphQL / tRPC / gRPC 의 schema-first contract.
CQRS — read model 과 write model 의 분리.
Strangler fig — legacy ↔ new system migration.
💻 패턴
Prisma — OR mismatch 흡수
// schema.prisma
// model User { id Int @id @default(autoincrement()) email String @unique posts Post[] }
import{PrismaClient}from"@prisma/client";constprisma=newPrismaClient();constuser=awaitprisma.user.findUnique({where:{email},include:{posts: true}});// 매 row → object graph (lazy/eager) automatic
DTO + Zod (API boundary)
import{z}from"zod";exportconstCreateUserDTO=z.object({email: z.string().email(),name: z.string().min(1).max(100),});exporttypeCreateUserDTO=z.infer<typeofCreateUserDTO>;// route
app.post("/users",async(req,res)=>{constdto=CreateUserDTO.parse(req.body);// 매 invalid 면 throw
constuser=awaituserService.create(dto);res.json(toUserResponse(user));// 매 entity → response DTO
});
Anti-Corruption Layer
// 매 legacy CRM 의 dirty model → 매 깨끗한 domain 으로
classCrmAcl{toCustomer(raw: LegacyCrmRow):Customer{return{id: CustomerId.of(raw.cust_id_v2??raw.cust_id),email: Email.of(raw.email_addr.trim().toLowerCase()),tier: raw.tier_code==="P"?"premium":"standard",};}}
Outbox (sync ↔ async)
awaitprisma.$transaction([prisma.order.create({data: order}),prisma.outbox.create({data:{topic:"order.created",payload: JSON.stringify(order)}}),]);// 매 별도 worker 가 outbox → Kafka publish (at-least-once)
Hexagonal port
// 매 domain side 의 port (interface)
exportinterfacePaymentGateway{charge(amount: Money,token: string):Promise<ChargeId>;}// 매 infra side 의 adapter
exportclassStripeAdapterimplementsPaymentGateway{asynccharge(amount: Money,token: string):Promise<ChargeId>{constr=awaitstripe.paymentIntents.create({amount: amount.cents,currency: amount.ccy,payment_method: token});returnChargeId.of(r.id);}}