JOIN 전략 (Inner/Outer)
관계형 데이터베이스의 핵심은 데이터를 정규화하여 나누고, 필요할 때 이를 다시 엮어서(JOIN) 조회하는 것이다.
JOIN은 두 개 이상의 테이블을 결합하여 데이터를 조회하는 SQL 연산이다. 관계형 데이터베이스에서 데이터는 정규화를 통해 여러 테이블로 분산되어 저장되므로, 의미 있는 정보를 얻기 위해서는 JOIN이 필수적이다.
JOIN 전략은 조회 결과뿐 아니라 성능에도 큰 영향을 미기 때문에 상황에 맞게 선택하는 것이 중요하다. 이번 포스팅에서는 실무에서 가장 빈번하게 사용되는 INNER JOIN과 OUTER JOIN의 차이점, 그리고 성능 최적화를 위한 고려사항을 정리해보았다.
대표적인 JOIN 방식은 다음 두 가지다.
- INNER JOIN
- OUTER JOIN (LEFT/RIGHT/FULL)
실무에서 가장 많이 사용되는 것은 INNER JOIN과 LEFT OUTER JOIN이다.
INNER JOIN (교집합)
두 테이블 모두에 존재하는 데이터만 조회하는 JOIN. 즉, 조건을 만족하는 교집합만 반환한다.
SELECT *
FROM order o
INNER JOIN product p
ON o.id = p.product_id
이 경우, post와 comment라는 양쪽 테이블 둘 다에 존재해야만 조회 결과에 나올 수 있다.
예시 : "주문 내역과 그 주문에 포함된 상품 정보를 가져와라"
LEFT OUTER JOIN (부분집합 + 차집합)
왼쪽 테이블의 데이터는 모두 유지하면서 오른쪽 테이블을 JOIN. 즉, 왼쪽 테이블을 기준으로 조회한다. LEFT OUTER JOIN은 왼쪽(기준) 테이블의 모든 행을 포함하고, 오른쪽 테이블에서 매칭되는 데이터가 있으면 결합하고, 없으면 NULL로 채운다.
SELECT *
FROM post p
LEFT (OUTER) JOIN comment c
ON p.id = c.post_id
comment가 없는 게시글도 조회되며, comment 칼럼은 NULL로 채워지게 된다.
글1 댓글
글2 NULL
예시: "모든 게시글을 가져오되, 댓글이 있으면 함께 보여줘라." (댓글이 없는 게시글도 나와야 하므로 LEFT JOIN 사용)
| JOIN 종류 | 핵심 정의 | 특징 | 예시 |
| INNER JOIN | 양쪽 테이블에 모두 데이터가 존재하는 행만 결합 | 교집합, 데이터가 한쪽이라도 없으면 결과에서 제외 | 결제 완료된 상품 목록 |
| LEFT OUTER JOIN | 왼쪽(기준) 테이블의 모든 행을 유지하며 오른쪽 데이터를 결합 | 합집합 성격, 오른쪽 데이터가 없으면 NULL로 채움 | 전체 회원 목록과 각 회원의 등급 정보 - 신규 회원은 등급이 없을 수 있음 |
언제 INNER JOIN을 쓰고 언제 OUTER JOIN을 쓸까
실무에서 위 둘이 자주 쓰이는 이유
일반적으로 실무에서 저 두가지 JOIN 전략을 자주 사용한다고 한다. 그 이유는 아래와 같다.
데이터의 방향성: 보통 우리 서비스의 데이터는 '회원 -> 주문 -> 결합 상품'처럼 명확한 상하 관계나 흐름이 있기 마련이다. 한쪽을 기준으로 데이터를 붙여 나가는 형식이기에 LEFT JOIN으로 충분하다.
보통 비즈니스 로직은 "A가 있을 때 B를 가져온다"는 기준이 있다. (예: 회원이 쓴 게시글, 게시글에 달린 댓글)
가독성: RIGHT JOIN은 테이블 순서만 바꾸면 LEFT JOIN으로 표현 가능하다. 쿼리는 왼쪽에서 오른쪽으로 읽히기 때문에 LEFT JOIN을 쓰는 것이 가독성이 훨씬 좋다
FULL OUTER JOIN의 위험성: 양쪽 테이블의 모든 데이터를 합치는 작업은 리소스를 엄청나게 소모하며, 실무에서 양쪽 다 데이터가 없을 수도 있는 상태를 한꺼번에 조회할 일이 거의 없다. 보통 유실된 데이터를 찾거나 통계 데이터를 뽑는 특수한 경우에만 사용한다. (MySQL 등 일부 DB는 지원하지 않아 UNION으로 구현하기도 한다.)
INNER JOIN을 쓰는 경우
연관 데이터가 반드시 존재해야하는 경우이다.
- 필수 관계일 때: 주문 정보를 조회하는데 사용자 정보가 반드시 있어야 하는 경우
- 양쪽 데이터 모두 존재가 보장될 때: 직원-부서처럼 부서가 없는 직원이 있을 수 없는 경우
- 성능 최적화: 불필요한 NULL 데이터를 제외하고 싶을 때
예시
- 주문 + 주문상품
- 댓글 + 작성자
- 리뷰 + 사용자
주문은 있는데 주문상품이 없다 -> 데이터 오류 발생
LEFT OUTER JOIN을 쓰는 경우
연관 데이터가 없어도 조회하는 경우이다.
- 선택적 관계일 때: 사용자 중 주문하지 않은 사람도 조회하고 싶을 때
- 데이터 누락 확인: "주문하지 않은 회원 찾기" 같은 분석 쿼리
- 기준 테이블의 완전성 보장: 모든 회원 목록을 보여주되, 주문 정보가 있으면 함께 표시
예시
- 게시글 + 댓글
- 상품 + 리뷰
- 사용자 + 프로필
게시글은 있지만 댓글이 없을 수 있음
FULL OUTER JOIN을 쓰는 경우
양쪽 테이블의 모든 행을 포함, 매칭 안 되는 부분은 NULL로 표시한다.
실무에서 드문 이유는 아래와 같다.
- 사용 케이스가 제한적: "양쪽 중 하나라도 있으면 모두 보여줘"라는 요구사항이 드물다.
- MySQL 미지원: MySQL은 FULL OUTER JOIN을 지원하지 않음 (LEFT + RIGHT UNION으로 구현해야 함)
- 성능 이슈: 두 번의 스캔이 필요하여 성능이 좋지 않음
- 대안이 명확함: 대부분의 경우 LEFT JOIN + UNION 또는 두 개의 별도 쿼리로 충분
CROSS JOIN을 쓰는 경우
카테시안 곱(Cartesian Product), 모든 조합을 생성해서 써야하는 경우이다.
예시
- 의도적으로 모든 조합이 필요할 때 (예: 날짜 테이블 × 상품 목록)
- 테스트 데이터 생성
실무 판단 기준
| 상황 | 조인 |
| 연관 데이터 필수 | INNER JOIN |
| 연관 데이터 선택 | LEFT JOIN |
| 시나리오 | JOIN 타입 | 이유 |
| 주문 목록 + 주문자 정보 | INNER | 주문자 없는 주문은 데이터 오류 |
| 전체 회원 목록 + 최근 주문 | LEFT | 주문 안 한 회원도 표시 필요 |
| 상품 목록 + 재고 정보 | INNER (보통) | 재고 없는 상품은 비활성 처리됨 |
| 게시글 + 댓글 수 | LEFT | 댓글 없는 게시글도 표시 |
| 직원 + 부서 | INNER (일반적) | 부서 없는 직원은 이례적 |
| 결제 내역 + 환불 정보 | LEFT | 환불 없는 결제가 대부분 |
JOIN이 성능에 미치는 영향
JOIN은 DB에서 비용이 가장 큰 연산 중 하나이다. 특히 JOIN 전략에 따라 처리 데이터 양이 달라진다.
데이터 증폭(Multiplication) - 1:N 문제
문제가 되는 쿼리 - 1:N
-- users(1) : orders(N)
SELECT u.user_id, u.name, o.order_id, o.amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id;
1:N 관계에서 조인을 통해 조회를 하면, 결과 행의 수가 N배로 늘어날 수 있다. 이는 메모리 점유율을 높여 성능 저하를 유발한다.
| user_id | name | order_id | amount |
|---------|-------|----------|--------|
| 1 | Alice | 101 | 5000 |
| 1 | Alice | 102 | 3000 |
| 1 | Alice | 103 | 7000 |
| 2 | Bob | 104 | 2000 |
원본 users 데이터:
- Alice 정보: 1행 (user_id=1, name='Alice')
- Bob 정보: 1행 (user_id=2, name='Bob')
JOIN 후 결과:
- Alice 정보: 3행 (주문 3개만큼 복제됨)
- Bob 정보: 1행
총 데이터량:
- users: 2행
- JOIN 결과: 4행 (2배 증가!)
문제: 주문이 많은 사용자는 여러 행으로 복제됨
- Alice가 주문 100개 → Alice 정보가 100번 반복
- 메모리 사용량 증가
- 네트워크 전송량 증가
// 메모리 낭비 예시
Alice의 name 컬럼 값 'Alice':
- 원본: 1번 저장 (5 bytes)
- JOIN 후: 3번 저장 (15 bytes)
실제로는 더 많은 컬럼이 있으므로:
- 원본 1행: 100 bytes
- JOIN 후 3행: 300 bytes (3배!)
집계 오류 가능성
-- 잘못된 집계 (심각한 버그!)
SELECT u.name, SUM(u.point) as total_points
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id;
-- Alice의 포인트가 1000이라면:
-- 결과: 3000 (1000 + 1000 + 1000) // 3배로 잘못 계산됨!
해결책: GROUP BY를 사용한 집계
이에 대한 해결책은 다음과 같다.
-- GROUP BY를 사용한 집계로 해결
SELECT u.name, COUNT(o.order_id) as order_count
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.name;
// 내부 동작 과정
// 1. 처음은 기존 문제되는 INNER JOIN 쿼리를 실행하게 되어 데이터 증폭 발생
// 2. GROUP BY 적용 (그룹화) - user_id로 그룹화한다.
[Group 1: user_id=1, name='Alice']
- order_id: 101, 102, 103
[Group 2: user_id=2, name='Bob']
- order_id: 104
// 3.집계 함수 실행
각 그룹별로 COUNT(o.order_id) 실행
[Group 1] COUNT(101, 102, 103) = 3
[Group 2] COUNT(104) = 1
Step 4: 최종 결과 (압축된 결과 도출)
------------------------
| name | order_count |
|-------|-------------|
| Alice | 3 | -> 3행이 1행으로 압축!
| Bob | 1 |
세부 동작 과정
세부 동작 과정은 다음과 같다.
### 2.2 왜 데이터 증폭이 해결되나?
**핵심 메커니즘:**
```
BEFORE GROUP BY:
================
메모리에 4행 존재:
Row 1: [user_id=1, name='Alice', order_id=101]
Row 2: [user_id=1, name='Alice', order_id=102]
Row 3: [user_id=1, name='Alice', order_id=103]
Row 4: [user_id=2, name='Bob', order_id=104]
총 메모리: 4행 × 100 bytes = 400 bytes
AFTER GROUP BY:
===============
메모리에 2행만 존재:
Row 1: [name='Alice', order_count=3]
Row 2: [name='Bob', order_count=1]
총 메모리: 2행 × 50 bytes = 100 bytes (75% 절감!)
```
**데이터베이스 내부 처리:**
```
1. Temporary Table 생성 (GROUP BY용)
+----------+-----------+-------------+
| user_id | name | order_count |
+----------+-----------+-------------+
2. JOIN 결과를 순회하며 집계
- (1, 'Alice', 101) 읽음 → 테이블에 (1, 'Alice', 1) 삽입
- (1, 'Alice', 102) 읽음 → 기존 행의 count를 2로 증가
- (1, 'Alice', 103) 읽음 → 기존 행의 count를 3으로 증가
- (2, 'Bob', 104) 읽음 → 테이블에 (2, 'Bob', 1) 삽입
3. 최종 Temporary Table
+----------+-----------+-------------+
| user_id | name | order_count |
+----------+-----------+-------------+
| 1 | Alice | 3 |
| 2 | Bob | 1 |
+----------+-----------+-------------+
4. 이 테이블 내용을 클라이언트로 전송
이 외에도 다양한 집계 함수를 활용하는 등의 방법들이 있다.
-- 여러 집계를 동시에
SELECT
u.user_id,
u.name,
COUNT(o.order_id) as order_count, -- 주문 수
SUM(o.amount) as total_amount, -- 총 주문 금액
AVG(o.amount) as avg_amount, -- 평균 주문 금액
MAX(o.order_date) as last_order_date, -- 마지막 주문일
MIN(o.amount) as min_order_amount -- 최소 주문 금액
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.name;
```
**결과:**
| user_id | name | order_count | total_amount | avg_amount | last_order_date | min_order_amount |
|---------|-------|-------------|--------------|------------|-----------------|------------------|
| 1 | Alice | 3 | 15000 | 5000 | 2024-03-09 | 3000 |
| 2 | Bob | 1 | 2000 | 2000 | 2024-03-08 | 2000 |
**데이터 압축 효과:**
```
원본 JOIN 결과: 4행
최종 결과: 2행 (50% 압축!)
각 행이 100 bytes라면:
- 전송 전: 400 bytes
- 전송 후: 200 bytes
해결책: 서브쿼리 사용
-- 또는 서브쿼리 사용
SELECT
u.user_id,
u.name,
(SELECT COUNT(*)
FROM orders o
WHERE o.user_id = u.user_id) as order_count
FROM users u;
**내부 동작 과정:**
```
Step 1: users 테이블 스캔
-----------------------------------------
| user_id | name |
|---------|-------|
| 1 | Alice | ← 현재 행
| 2 | Bob |
Step 2: 각 행마다 서브쿼리 실행
-----------------------------------------
현재 행: user_id=1, name='Alice'
서브쿼리 실행:
SELECT COUNT(*) FROM orders WHERE user_id = 1
orders 테이블에서 user_id=1인 행 검색:
| order_id | user_id |
|----------|---------|
| 101 | 1 | ✓
| 102 | 1 | ✓
| 103 | 1 | ✓
COUNT(*) 결과: 3
현재 행에 결과 추가:
| user_id | name | order_count |
|---------|-------|-------------|
| 1 | Alice | 3 |
Step 3: 다음 행으로 이동
-----------------------------------------
현재 행: user_id=2, name='Bob'
서브쿼리 실행:
SELECT COUNT(*) FROM orders WHERE user_id = 2
orders 테이블에서 user_id=2인 행 검색:
| order_id | user_id |
|----------|---------|
| 104 | 2 | ✓
COUNT(*) 결과: 1
현재 행에 결과 추가:
| user_id | name | order_count |
|---------|-------|-------------|
| 2 | Bob | 1 |
Step 4: 최종 결과
-----------------------------------------
| user_id | name | order_count |
|---------|-------|-------------|
| 1 | Alice | 3 |
| 2 | Bob | 1 |
```
### 3.2 왜 데이터 증폭이 없나?
**핵심 차이점:**
```
JOIN 방식:
==========
1. users와 orders를 먼저 JOIN (카테시안 곱 발생)
2. 결과: users 1행 × orders 3행 = 3행 (증폭!)
3. GROUP BY로 다시 압축
서브쿼리 방식:
=============
1. users 1행씩 처리
2. 각 행마다 독립적으로 orders 집계
3. 결과: users 1행 → 결과 1행 (증폭 없음!)
```
**메모리 사용 비교:**
```
JOIN + GROUP BY:
================
임시 메모리 사용:
- JOIN 결과 저장: 4행 × 100 bytes = 400 bytes
- GROUP BY 임시 테이블: 2행 × 50 bytes = 100 bytes
- 총: 500 bytes
서브쿼리:
=========
임시 메모리 사용:
- users 현재 행: 1행 × 100 bytes = 100 bytes
- 서브쿼리 결과: 1개 숫자 = 8 bytes
- 최종 행: 1행 × 50 bytes = 50 bytes
- 총: 158 bytes (한 번에 하나씩 처리)
메모리 효율: 서브쿼리 방식이 더 절약적!
(중요) 실전 예시: JPA에서의 활용
// N+1 문제 발생 (데이터 증폭과 유사한 맥락)
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
// 쿼리 실행
List<User> users = userRepository.findAll(); // 1번 쿼리
for (User user : users) {
int count = user.getOrders().size(); // N번 쿼리 (지연 로딩)
}
// 총: 1 + N번 쿼리
해결: FETCH JOIN(GROUP BY와 유사)
// 여기서 쿼리를 딱 1번만 실행!
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();
// 실행되는 SQL (데이터 증폭 발생!)
SELECT u.*, o.*
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;
// 결과:
// Alice가 주문 3개 → User 객체가 3번 조회됨
// JPA가 내부적으로 중복 제거 처리
// 2. DB에서 반환되는 결과셋 (Row 레벨)
| user_id | name | email | order_id | amount | order_date |
|---------|-------|----------------|----------|--------|------------|
| 1 | Alice | alice@mail.com | 101 | 5000 | 2024-03-01 |
| 1 | Alice | alice@mail.com | 102 | 3000 | 2024-03-02 |
| 1 | Alice | alice@mail.com | 103 | 7000 | 2024-03-03 |
| 2 | Bob | bob@mail.com | 201 | 4000 | 2024-03-01 |
| 3 | Charlie| c@mail.com | NULL | NULL | NULL |
JOIN 순서와 실행 계획
JOIN하는 순서와 실행 계획 등도 성능에 영향을 준다. 아래 예시를 조회한다고 가정해보자.
- users: 100만 행
- orders: 1억 행
- recent_orders (최근 1주일): 10만 행
비효율적인 쿼리는 전체 order를 먼저 JOIN한다.
-- 전체 orders를 먼저 JOIN
SELECT u.name, o.amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
WHERE o.order_date >= '2024-03-01';
최적화된 쿼리는 아래와 같이 조건으로 먼저 필터링하여 조회한다.
JOIN하기 전에 먼저 필터링!
-- 조건으로 먼저 필터링
SELECT u.name, recent.amount
FROM users u
INNER JOIN (
SELECT user_id, amount
FROM orders
WHERE order_date >= '2024-03-01'
) recent ON u.user_id = recent.user_id;
-- 또는 인덱스 활용
CREATE INDEX idx_order_date ON orders(order_date, user_id);
INNER JOIN 성능 특징
- 불필요한 행이 제거됨
- (양쪽 테이블이 둘 다 존재해야하기 때문에) 결과 데이터 양이 적어짐
- 일반적으로 LEFT JOIN보다 빠름
LEFT OUTER JOIN 성능 특징
- 왼쪽 테이블의 모든 데이터를 유지
- NULL 데이터 포함
- 결과 데이터가 많아질 수 있음
- LEFT JOIN은 INNER JOIN보다 느릴 수 있음:
문제1 - NULL 생성 오버헤드
-- INNER: 매칭되는 10만 행만
-- LEFT: 전체 100만 행 + NULL 처리
SELECT u.*, o.amount
FROM users u -- 100만
LEFT JOIN orders o ON u.user_id = o.user_id;
문제2 - 인덱스 활용 제약
-- INNER JOIN: 인덱스 최적화 가능
-- LEFT JOIN: 왼쪽 테이블 전체 스캔 필요
최적화 방법 - 인덱스 전략
최적화 방법으로 EXISTS를 사용하거나, 필요한 경우에만 LEFT JOIN을 쓰는 경우가 있지만, JOIN 성능의 핵심은 인덱스이다.
-- JOIN 조건 컬럼에 인덱스 필수
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_users_user_id ON users(user_id); -- PK라면 자동 생성됨
-- 복합 인덱스로 커버링 인덱스 구성
CREATE INDEX idx_orders_covering
ON orders(user_id, order_date, amount);
-- 이제 이 쿼리는 테이블 접근 없이 인덱스만으로 처리
SELECT user_id, order_date, amount
FROM orders
WHERE user_id = 123;
인덱스(INDEX)의 중요성
조인 조건이 되는 컬럼(FK 등)에 인덱스가 없으면 DB는 전체 테이블을 다 뒤져야 하는 Full Table Scan을 수행한다. 데이터가 많을수록 조인 성능은 인덱스가 결정하게 된다.
(중요) NULL 처리 주의사항
LEFT JOIN 후 WHERE 절의 함정
LEFT JOIN을 사용할 때 가장 많이 발생하는 실수가 WHERE 조건이다.
아래 예시는 comment가 존재하지 않는다는 조건을 가정하고 있다. 이렇게 되면 사실상 INNER JOIN의 결과와 동일하게 된다. 왜냐면 NULL 행이 WHERE에서 제거되기 때문이다.
SELECT *
FROM post p
LEFT JOIN comment c
ON p.id = c.post_id
WHERE c.id IS NOT NULL
올바른 필터링
필터 조건은 ON 절에 넣어야 한다. WHERE 대신 AND
이렇게 해야 아래와 같이 조회가 가능하다.
- 댓글 없는 게시글 유지
- 삭제 댓글만 필터
SELECT *
FROM post p
LEFT JOIN comment c
ON p.id = c.post_id
AND c.is_deleted = false
// 결과: 모든 사용자 + 삭제되지 않은
그 외 다양한 NULL 처리 방법들
NULL 명시적 처리
-- COALESCE: NULL을 기본값으로 치환
SELECT
u.name,
COALESCE(o.amount, 0) as amount,
COALESCE(o.order_date, 'No order') as order_status
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;
-- NULLIF: 역으로 특정 값을 NULL로
SELECT NULLIF(u.phone, '') FROM users u;
-- CASE로 세밀한 제어
SELECT
u.name,
CASE
WHEN o.amount IS NULL THEN '미주문'
WHEN o.amount = 0 THEN '무료주문'
ELSE '정상주문'
END as order_type
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;
집계 함수와 NULL
-- COUNT(*) vs COUNT(column)
SELECT
u.name,
COUNT(*) as total_rows, -- NULL 포함 행 수
COUNT(o.order_id) as order_count -- NULL 제외 주문 수
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.name;
-- AVG, SUM도 NULL 자동 제외
SELECT
u.user_id,
AVG(o.amount) as avg_order -- NULL인 사용자는 NULL 반환 (0이 아님!)
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id;
JPA에서의 NULL 처리
// ❌ NPE 위험
@Query("SELECT u.name, o.amount FROM User u LEFT JOIN u.orders o")
List<Object[]> findUsersWithOrders();
// result[1]이 NULL일 수 있음!
// ✅ DTO로 안전하게 처리
@Query("SELECT new com.example.dto.UserOrderDto(u.name, COALESCE(o.amount, 0)) " +
"FROM User u LEFT JOIN u.orders o")
List<UserOrderDto> findUsersWithOrders();
// DTO
public class UserOrderDto {
private String name;
private Long amount; // primitive long 대신 Long 사용
public UserOrderDto(String name, Long amount) {
this.name = name;
this.amount = amount != null ? amount : 0L;
}
}
실무 체크리스트
JOIN 작성 전 확인사항
- 기준 테이블의 모든 행이 필요한가? → LEFT JOIN
- 양쪽 데이터 모두 필수인가? → INNER JOIN
- JOIN 조건 컬럼에 인덱스가 있는가?
- 1:N JOIN으로 데이터 증폭이 예상되는가?
- LEFT JOIN 후 WHERE에서 NULL 필터링하고 있지 않은가?
- NULL 처리가 명시적으로 되어 있는가?
- EXPLAIN으로 실행 계획을 확인했는가?
성능 최적화 체크리스트
- 필요한 컬럼만 SELECT (SELECT * 지양)
- JOIN 전 WHERE로 데이터 먼저 필터링
- 서브쿼리보다 JOIN이 효율적인지 확인
- 집계 쿼리에서 GROUP BY 인덱스 활용
- 페이징 시 커버링 인덱스 고려
- EXPLAIN 결과에서 filesort, temporary 확인
'TIL' 카테고리의 다른 글
| [TIL] 레이어드 아키텍처 & 책임 분리 (0) | 2026.03.04 |
|---|---|
| [TIL] DDD 구성요소 (0) | 2026.02.24 |
| [TIL] 전략적 DDD와 전술적 DDD (0) | 2026.02.23 |
| [TIL] DDD 개념 & 설계 사고방식 (0) | 2026.02.20 |
| [TIL] 블로킹/논블로킹과 동기/비동기 (0) | 2026.02.10 |