λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ›οΈ Architecture

[이벀트 μ†Œμ‹±κ³Ό λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜] CH.1 도메인 주도 섀계

by GroovyArea 2025. 2. 13.

ν˜„μž¬ λ‹€λ‹ˆκ³  μžˆλŠ” νšŒμ‚¬λŠ” μ‹€ - νŒ€ ꡬ쑰둜 λ‚˜λ‰˜μ–΄ μžˆλ‹€.
λ‚˜λŠ” μ œν’ˆμ‹€ λ°±μ—”λ“œ μ—”μ§€λ‹ˆμ–΄μΈλ°, 격주둜 μ œν’ˆμ‹€ λ°±μ—”λ“œ 회의λ₯Ό μ§„ν–‰ν•œλ‹€.
 
ν˜„μž¬ 속해 μžˆλŠ” νŒ€μ—μ„œ ν”„λ‘ νŠΈμ—”λ“œ 뢄듀은 μ œν’ˆμ‹€ ν”„λ‘ νŠΈμ—”λ“œ 개발자 뢄듀끼리 ν…ŒμŠ€νŠΈ κ΄€λ ¨ μŠ€ν„°λ””λ₯Ό ν•˜μ‹ λ‹€κ³  ν•˜μ—¬,
λ°±μ—”λ“œλŠ” λ‚΄κ°€ ν•œλ²ˆ μŠ€ν„°λ””λ₯Ό μ œμ•ˆν•˜λ©΄ μ–΄λ–¨κΉŒλž€ 생각이 λ“€μ—ˆλ‹€!
회의 λ•Œ, μŠ€ν„°λ”” μ£Όμ œμ™€ μ‹œμž‘μ— λŒ€ν•΄μ„œ μ†ŒμŠ€λ₯Ό 던쑌고, λ‚˜ 포함 5λͺ…μ΄μ„œ μ œν’ˆμ‹€ λ°±μ—”λ“œ μŠ€ν„°λ””λ₯Ό μ§„ν–‰ν•˜κ²Œ λ˜μ—ˆλ‹€.
 
ν˜„μž¬ νŒ€ & μ‹€ λ³„λ‘œ 마이크둜 μ„œλΉ„μŠ€λ₯Ό 운영 μ€‘μ΄λ―€λ‘œ,
첫 μ‹œμž‘μ€ 비ꡐ적 κ°„κ²°ν•˜κ³  μ΅μˆ™ν•œ κ°œλ…μΈ 이벀트 μ†Œμ‹± ν™œμš©ν•œ 마이크둜 μ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜μ— λŒ€ν•œ 주제둜 μ§„ν–‰ν•˜κ²Œ λ˜μ—ˆλ‹€.
λ‚΄κ°€ μ§„ν–‰ν•˜λŠ” μŠ€ν„°λ””λ§Œ 뭐 거의 3개인데..  κ·Έλž˜λ„ 같이 ν•˜λ©΄ λ‹€ 쒋은 거지~~γ…Žγ…Ž
이 μŠ€ν„°λ””λ₯Ό λΉ λ₯΄κ²Œ 마치고, νšŒμ‚¬ μ†ŒμŠ€μ— μ μš©ν• λ§Œν•œ ν…ŒμŠ€νŠΈ κ΄€λ ¨ μ£Όμ œλ‚˜ 인프라 μͺ½μœΌλ‘œ λ„˜μ–΄κ°€λ©΄ 정말 μž¬λ―Έμžˆμ„ 것 κ°™λ‹€!
 
μŠ€ν„°λ”” μ€€λΉ„λ₯Ό ν•˜λ©°, 책을 μ½μ—ˆλŠ”λ° 이걸 개인 λΈ”λ‘œκ·Έμ— κΈ°λ‘ν•˜λ©΄ μ–΄λ–¨κΉŒν•˜μ—¬ 맀 Chapter λ₯Ό 읽으며 λ‚΄ κ²½ν—˜κ³Ό 생각을 정리할 κ³„νš!
 

첫 μž₯, 도메인 주도 섀계

μ·¨μ€€, μ‹ μž… λ•Œ 정말 많이 κ³΅λΆ€ν–ˆλ˜ κ°œλ…μΈλ°, μ΄μ œμ„œμ•Ό μ •λ¦¬ν•œλ‹€.
 
λͺ¨λΈμ΄λΌλŠ” κ°œλ….
Grady Booch λŠ” "λ‹¨μˆœνžˆ ν‘œν˜„ν•œ μ‹€μ œ 세계" 라고 λΆ€λ₯Έλ‹€κ³  ν•œλ‹€.
λͺ¨λΈμ€ κ΄€μ‹¬μ‚¬μ˜ 뢄리가 μ€‘μš”ν•˜λ‹€. λͺ¨λΈμ„ λ°”λΌλ³΄λŠ” 관심사에 따라 λͺ¨λΈμ˜ μ •μ˜κ°€ 달라지기도 ν•œλ‹€.
μš°λ¦¬λŠ” κ°œλ°œμžμ΄λ―€λ‘œ, κΈ°νšμžλ‚˜ λ§ˆμΌ€ν„°λ“± "업무 λ‹΄λ‹Ήμž" 의 μ‹œμ„ μ„ 더 깊히 μ΄ν•΄ν•˜λ € λ…Έλ ₯ν•˜κ³  λͺ¨λΈμ„ 섀계해야 ν•œλ‹€.
 
μ„œλΉ„μŠ€ λ ˆμ΄μ–΄ νŒ¨ν„΄.
μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ œκ³΅ν•˜λŠ” μ‚¬μš© κ°€λŠ₯ν•œ κΈ°λŠ₯ 집합
λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μΊ‘μŠν™” 및 ν΄λΌμ—κ²Œ 영ν–₯을 주지 μ•Šλ˜ λ³€ν™”λ₯Ό μˆ˜μš©ν•˜κ±°λ‚˜ μ„±λŠ₯ κ°œμ„ μ˜ ν™œλ™μ„ κ°€λŠ₯ν•˜κ²Œ ν•œλ‹€.
이λ₯Ό ν…Œλ©΄, Restful API 와 Socket μ΄λ‚˜ grpc λ“±μ˜ ν”„λ‘œν† μ½œμ„ μš”κ΅¬ν•˜λŠ” ν΄λΌμ΄μ–ΈνŠΈλ₯Ό μΆ©μ‘±μ‹œν‚¬ 수 μžˆλ‹€.
 
도메인 λͺ¨λΈ νŒ¨ν„΄.
μš”κ΅¬μ‚¬ν•­μ΄ λ‹€μ–‘ν•΄ 짐에 따라, μœ μ§€λ³΄μˆ˜μ„±μ΄ μ–΄λ €μ›Œμ§„λ‹€.
도메인 λͺ¨λΈ μ•ˆμ— 이런 λΉ„μ¦ˆλ‹ˆμŠ€λ₯Ό λΆ„λ¦¬ν•œλ‹€.

public class CartService {

	public void addItem(String cartId, String ProductName, int quantity) {
    	int size = CartDao.selectItemSize(cartId);
        
        if (size >= 10) {
        	throw RuntimeException("..");
        }
    }
}
 
 // λ„ˆλ¬΄λ‚˜ 유λͺ…ν•œ νŒ¨ν„΄,, 그리고 μ˜€λžœλ§Œμ΄λ„€ μžλ°” μ½”λ“œ,,

 
이러면 λ³€κ²½ μš”μ²­μ— μœ μ—°ν•˜κ²Œ λŒ€μ‘ κ°€λŠ₯ν•˜λ‹€. μœ μŠ€μΌ€μ΄μŠ€μ™€ λ³„κ°œλ‘œ~!
 
ν—₯사고날 μ•„ν‚€ν…μ²˜
μ›Œλ‚™ 유λͺ…ν•œ input, output adaptor 와 usecase 및 domain & business logic  그리고 infra layer
 
μ—”ν‹°ν‹°
κ°œλ°œμžλŠ” 도메인 객체보닀 데이터에 μ§‘μ€‘ν•˜λŠ” κ²½ν–₯이 μžˆλŠ”λ°, 이런 κ²½ν–₯성을 가진 κ°œλ°œμžλŠ” Entity λ₯Ό Table 둜 κ°„μ£Όν•˜λ € ν•œλ‹€.
ν…Œμ΄λΈ”κ³Ό 관계 쀑심을 κ°€μ§€λŠ” 데이터 λͺ¨λΈμ„ λ¨Όμ € λ§Œλ“€κ³ , ν…Œμ΄λΈ”μ— λŒ€μ‘ν•˜λŠ” 클래슀둜 λ§€ν•‘ν•˜λ©΄ Entity λŠ” κ°€μž₯ 기본적인 μ±…μž„μ„ 가진 Information Holder 역할을 κ°€μ Έ getter, setter 만 μ œκ³΅ν•œλ‹€.
Entity λŠ” μ‹λ³„μžκ°€ μ‘΄μž¬ν•˜λ©°, 생애주기λ₯Ό 가진닀. 이λ₯Ό ν…Œλ©΄, id 와 no 등이 μžˆκ² λ‹€.
μ΄λŠ” 고유 번호둜 UUID λ‘œλ„ 많이 μ‚¬μš©ν•˜λŠ” κ²½μš°κ°€ μžˆλ‹€.
 
κ°’ 객체
Entity 와 λ‹€λ₯΄κ²Œ, μ‹λ³„μžκ°€ ν•„μš” μ—†λŠ” 객체이닀.
이름에 걸맞게 Entity λ₯Ό μˆ˜μ‹ν•˜λŠ” 속성이라 μƒκ°ν•˜λ©΄ νŽΈν•˜λ‹€.

public class Product {
    
    private String id;
    private String no;
    private String name;

    private Volume volume;
    
}

public class Volume {
    
    private int width;
    private int height;
    private int depth;
    
    public Volume(int width, int height, int depth) {
        this.width = width;
        this.height = height;
        this.depth = depth;
    }
}

 
μ΄λ ‡κ²Œ μ •μ˜ν•˜λ©΄ μ–΄λ–¨κΉŒ? Volume 은 κ°’ κ°μ²΄μ΄λ―€λ‘œ λΆˆλ³€μ„±μ„ λˆλ‹€. 각 ν•„λ“œλ₯Ό final 둜 μ„ μ–Έν•˜λŠ” 것은 κΈˆμƒμ²¨ν™”μΌ 것이닀.
λ§Œμ•½ Product 의 Volume 이 λ³€κ²½ λ˜λŠ” μš”κ΅¬μ‚¬ν•­μ— λŒ€μ‘ν•˜λ €λ©΄ μƒˆλ‘œμš΄(new) Volume 을 λ„£μ–΄μ€˜μ•Ό ν•œλ‹€.
κ°’ 객체에도 λ‹Ήμ—°νžˆ λΉ„μ¦ˆλ‹ˆμŠ€λ₯Ό μΊ‘μŠν™”κ°€ κ°€λŠ₯ν•˜λ‹€. λ˜μ„±μžλ‘œ~!

public class Volume {

    private int width;
    private int height;
    private int depth;

    public Volume(int width, int height, int depth) {
        if(width * 1.5 >= depth) {
            throw new RuntimeException("Nagative!");
        }
        
        this.width = width;
        this.height = height;
        this.depth = depth;
    }
}

μ‹€μš© 주의~
 
μΈν”„λΌμŠ€νŠΈλŸ­μ²˜ μ„œλΉ„μŠ€
ν—₯μ‚¬κ³ λ‚ μ—μ„œ μ •μ˜ν•œ μ–΄λŒ‘ν„°μ΄λ‹€. 
DB, Network, API call, Mail sending, Event publishing λ“±μ˜ 기술적인 업무λ₯Ό 닀룬닀.
이 μ±…μž„μ„ λ‹€λ₯Έ μ„œλΉ„μŠ€μ™€ λΆ„λ¦¬ν•˜μ§€ λͺ»ν•˜λ©΄ μˆœμˆ˜ν•˜κ³  κΉ¨λ—ν•œ 도메인 λͺ¨λΈμ„ μΉ¨λ²”ν•˜κ²Œ 될 것이닀. 그둜 인해 독립적인 ν…ŒμŠ€νŠΈκ°€ λΆˆκ°€λŠ₯!
 
μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλΉ„μŠ€
λ„ˆλ¬΄λ„ 유λͺ…ν•œ κ·Έ usecase.
νŠΈλžœμž­μ…˜ 관리, μΈν”„λΌμŠ€νŠΈλŸ­μ²˜μ™€μ˜ μƒν˜Έμž‘μš©μ„ ν¬ν•¨ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ usecase 의 흐름을 μ‘°μ •ν•œλ‹€.
μš”μ•½ν•˜μžλ©΄ ν΄λΌμ΄μ–ΈνŠΈμ˜ μš”μ²­λΆ€ν„° μ‘λ‹΅κΉŒμ§€μ˜ 일련의 νŠΈλžœμž­μ…˜μ„ μ²˜λ¦¬ν•œλ‹€.
도메인 객체의 μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜λŠ” 것도 ν•˜λ‚˜μ˜ μ±…μž„μ΄λ‹€.
logging, metrics, monitoring 도 μ±…μž„μ΄λΌκ³  ν•œλ‹€.
 
도메인 μ„œλΉ„μŠ€
Eric Evans : Entity 에 λΆ€μ—¬ν•˜κΈ° μ ν•©ν•˜μ§€ μ•Šμ€ μ±…μž„μ„ κ°€μ§€λŠ” 객체.
말 κ·ΈλŒ€λ‘œ 도메인을 μœ„ν•œ κ°μ²΄μ΄λ―€λ‘œ, 순수 Pojo 둜 κ΅¬ν˜„λ˜μ–΄μ•Ό ν•œλ‹€.
이λ₯Ό ν…Œλ©΄, Multi module 상 core λͺ¨λ“ˆμ—μ„œ Spring μ˜μ‘΄μ„±μ΄ λΉ μ Έμ•Ό ν•˜λŠ” μ΄μœ μ΄λ‹€.
 
μ€‘μš”ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ ν”„λ‘œμ„ΈμŠ€λ₯Ό μˆ˜ν–‰ν•˜λ €κ±°λ‚˜,
μ–΄λ–€ μ»΄ν¬μ§€μ…˜μ—μ„œ λ‹€λ₯Έ μ»΄ν¬μ§€μ…˜μœΌλ‘œ 도메인 객체λ₯Ό λ³€ν™˜ν•˜κ±°λ‚˜,
ν•˜λ‚˜ μ΄μƒμ˜ 도메인 κ°μ²΄μ—μ„œ μš”κ΅¬ν•˜λŠ” μž…λ ₯ 값을 계산할 λ•Œ!
 
μΆ”κ°€λ‘œ 도메인 μ„œλΉ„μŠ€μ˜ λͺ…칭은 Service λ₯Ό ν¬ν•¨ν•˜κΈ°λ³΄λ‹€, 문제 μ˜μ—­μ—μ„œ 클래슀 역할을 더 λͺ…ν™•νžˆ ν‘œν˜„ν•˜λŠ” 이름이 λ‚«λ‹€.

public class ProductCreationPolicy {
    
    public boolean isValid(Product product) {
        // λΉ„μ¦ˆλ‹ˆμŠ€ 둜직
        return true;
    }
}

 
이 외에도, νƒ€μž…μ˜ λ³€ν™˜, νŠΉμ • 도메인 객체λ₯Ό μ‚¬μš©ν•˜κ±°λ‚˜ DTO 둜 λ³€ν™˜ν•˜κ±°λ‚˜ λ°˜λŒ€μ˜ 경우일 λ•Œ μ‚¬μš©ν•œλ‹€.
 
μ• κ·Έλ¦¬κ±°νŠΈ
도메인 κ°μ²΄λ“€μ˜ μ—°κ΄€ κ΄€κ³„μ—μ„œ λΆˆλ³€μ‹(invariants)을 보μž₯ν•΄μ•Ό ν•˜λŠ” λ‹¨μœ„
μ—¬λŸ¬κ°œμ˜ Entity, VO 둜 ꡬ성 되며 포함할 수 μžˆλ‹€. Aggregate λ₯Ό λŒ€ν‘œν•˜λŠ” Entity λŠ” Aggregate Root 라고도 λΆ€λ₯Έλ‹€.
Aggregate λŠ” μš”κ΅¬μ‚¬ν•­κ³Ό 업무 μ§€μ‹œμžμ˜ μ‹œκ°μ— 따라 λ‹¬λΌμ§€λŠ” κ°œλ…μ΄λ―€λ‘œ, λͺ…ν™•ν•œ ν…μŠ€νŠΈμ˜ μ •μ˜ μ—†μ΄λŠ” λͺ¨ν˜Έν•œ κ°œλ…μ΄λΌκ³ λ„ λΆ€λ₯Έλ‹€.
 
μΌλ‘€λ‘œ Cart, Item, Product λΌλŠ” Entity κ°€ μžˆλŠ”λ°, Cart κ°€ Aggregate Root κ°€ λ˜μ—ˆμ„ λ•Œμ˜ μ˜ˆμ‹œμ΄λ‹€.

public class Cart {
    
    private String cartId;
    private List<Item> items;
    
    public Cart(String cartId) {
        this.cartId = cartId;
        this.items = new ArrayList<>();
    }
    
    public void addItem(Product product, int quantity) {
        Item newItem = new Item(product, quantity);
        this.items.add(newItem);
    }
    
    ///
    
    public boolean isEmpty() {
        return this.items.isEmpty();
    }
}

 
μ΄λ ‡κ²Œ Aggregate Root λ₯Ό μ •μ˜ν•˜κ³ , Usecase (application service) μ—μ„œλŠ” Aggregate λ₯Ό 톡해 μš”κ΅¬μ‚¬ν•­μ„ μˆ˜ν–‰ν•˜λŠ” 일련의 κ³Όμ •λ§Œ λ‚˜μ—΄ν•˜λ©΄ λœλ‹€.
μ΄λ ‡κ²Œ μ„€κ³„ν•œ Aggregate λŠ” λ‹¨μœ„ ν…ŒμŠ€νŠΈμ—λ„ 훨씬 μš©μ΄ν•˜λ‹€. μ±…μž„μ΄ 뢄리 λ˜μ–΄ 있기 λ•Œλ¬Έ.
 
섀계 κ·œμΉ™μœΌλ‘œ,
- λΉ„μ¦ˆλ‹ˆμŠ€ λΆˆλ³€μ‹μ„ μ• κ·Έλ¦¬κ±°νŠΈλ‘œ ν•œμ •
- μž‘μ€ μ• κ·Έλ¦¬κ±°νŠΈλ‘œ 섀계
- λ‹€λ₯Έ μ• κ·Έλ¦¬κ±°νŠΈλŠ” μ‹λ³„μžλ‘œ μ°Έμ‘°
- μ• κ·Έλ¦¬κ±°νŠΈκ°„ λ³€ν™”λŠ” 결과적 일관성을 μ‚¬μš©
 
μΆ”μƒν™”λœ 핡심
μ•žμ„œ λ§ν•˜ μ—¬λŸ¬ κ°œλ…μ„ 싀무에 μ μš©ν•˜κΈ°λŠ” ν•œκ³„κ°€ μžˆλ‹€.
이런 λΉŒλ”© 블둝 외에 μ€‘μš”ν•œ 것 쀑 ν•˜λ‚˜λŠ” Abstract Core 이닀.
 
Eric Evans 도메인 주도 섀계 μ €μ„œ 쀑 이런 사둀λ₯Ό μ†Œκ°œν•œλ‹€.
1. μ„ λ°•μ—μ„œ μ°¨λŸ‰μœΌλ‘œ 직접 이동
2. μ„ λ°•μ—μ„œ 야적μž₯의 νŠΉμ • μœ„μΉ˜λ‘œ 이동
3. 야적μž₯의 νŠΉμ • μœ„μΉ˜μ—μ„œ 야적μž₯의 λ‹€λ₯Έ μœ„μΉ˜λ‘œ 이동
4. 야적μž₯의 νŠΉμ • μœ„μΉ˜μ—μ„œ μ°¨λŸ‰μœΌλ‘œ 이동
 
Entity λŠ” μ»¨ν…Œμ΄λ„ˆ, μ„ λ°•, 야적μž₯, μ°¨λŸ‰, 크레인 등이 μžˆλ‹€.
μ–΄λ–»κ²Œ 섀계할 것인가?

    public inteface MovingService {
        void move(Container container, Location from, Location to);
    }

 
정닡은 μ—†λ‹€. ν•˜μ§€λ§Œ λͺ¨λ“  Entity λ₯Ό ν¬ν•¨ν•˜κΈ°λ³΄λ‹€ Usecase μ—μ„œλŠ” μ΄λ ‡κ²Œ κ°„κ²°ν•˜κ²Œ ν‘œν˜„μ΄ κ°€λŠ₯ν•˜λ‹€.
λͺ¨λΈλ§μ„ μ‰½κ²Œ ν•˜λ©°, μ˜€νΌλ ˆμ΄μ…˜λ„ λ‹¨μˆœν•΄μ‘Œλ‹€.
 


 
도메인 주도 섀계, μ°Έ 많이 κ³΅λΆ€ν•œ κ°œλ…μ΄λ‹€. κ°„λž΅ν•˜μ§€λ§Œ 직접 μ •λ¦¬ν•˜λŠ” 건 μ²˜μŒμ΄λ‹€.
λ‹€μŒ μ±•ν„°λŠ” 객체지ν–₯ 섀계 원칙인데, 이건 μ•Œλ©΄μ„œλ„ 항상 μ μš©ν•˜κ³ , 특히 μ±…μž„ μžˆλŠ” 객체둜 뢄리 및 섀계가 쉽지 μ•Šλ‹€.
λ‹€μŒ μž₯도 재밌게 μ½μ–΄λ³΄μž~!
 
좜처 : http://www.acornpub.co.kr/book/microservices-eventsourcing  
그리고 λ‚˜

이벀트 μ†Œμ‹±κ³Ό λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜

이벀트 μ†Œμ‹±μ„ μ΄ν•΄ν•˜κΈ° μœ„ν•΄ 도메인 주도 섀계와 객체지ν–₯ 섀계 원칙(SOLID)을 μ—°κ³„ν•΄μ„œ μ„€λͺ…ν•˜λŠ” 책이닀.

www.acornpub.co.kr

λ°˜μ‘ν˜•