포스트

C# Clean Architecture + DI 구조 설계하기 (1) - 프로젝트 구조 잡기

프로젝트를 처음 만들 때는 보통 이렇게 시작한다.

Controller에서 바로 DB 호출하고
Service 하나 만들어서 이것저것 다 넣고
Repository 없이 DbContext를 직접 사용하고…

처음엔 빠르고 편하다.

근데 기능이 조금만 늘어나면
코드가 서로 엉키기 시작한다

  • 수정 하나 하면 여러 파일 같이 터짐
  • 테스트 거의 불가능
  • 유지보수 난이도 급상승

그래서 결국 필요해지는 게

Clean Architecture 구조다


Clean Architecture가 뭐냐?

한 줄로 정리하면 이거다.

의존성 방향을 “안쪽으로만” 흐르게 만드는 구조

조금 풀어서 보면

  • 핵심 비즈니스 로직은 중심에 둔다
  • 바깥쪽(외부 기술)은 언제든 교체 가능하게 만든다

전체 구조

Clean Architecture는 보통 4단계로 나눈다.

  • Domain → 핵심 비즈니스 (Entity)
  • Application → 유스케이스 (Service, Interface)
  • Infrastructure → DB, 외부 API
  • Presentation → Controller, API

핵심: 의존성 방향

여기서 제일 중요한 포인트

1
2
Presentation  Application  Domain
Infrastructure  Application  Domain

▶ 무조건 안쪽만 바라본다

  • Domain → Infrastructure 의존 금지
  • Application → DbContext 직접 사용 금지

이거 하나만 지켜도 구조가 거의 무너지지 않는다.

솔루션 구조 만들기

Visual Studio 기준으로 프로젝트를 이렇게 나눈다.

1
2
3
4
5
6
MyProject.sln

- MyProject.Domain
- MyProject.Application
- MyProject.Infrastructure
- MyProject.Api

▶ 처음엔 좀 과해 보이지만 프로젝트 커질수록 이 구조가 살려준다

1. Domain (핵심 계층)

가장 안쪽, 가장 중요한 계층이다.

1
2
3
4
5
public class User
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

▶ 규칙

순수한 객체만 존재 DB 코드 없음 외부 라이브러리 의존 없음

즉,

▶ “절대 안 깨져야 하는 영역”

2. Application (유스케이스 계층)

유스케이스 계층?

유스케이스

한국스마트그리드협회 참조

1
2
3
유스케이스 계층은 시스템이 수행해야 할 ‘행위(시나리오)’를 중심으로, 계층형 아키텍처에서 유스케이스를 분리·구조화하는 관점.
스마트그리드 표준화 프레임워크 3.0에서는 계층적 구조에 따라 비즈니스 유스케이스(BUC)와 시스템 유스케이스(SUC)로 구분해 설명합니다
참조 : 한국스마트그리드협회

여기서 실제 비즈니스 흐름이 정의된다.

public interface IUserRepository { Task<User?> GetByIdAsync(Guid id); }

▶ 핵심 포인트

Repository는 인터페이스만 존재 구현은 절대 여기 있으면 안 됨

왜냐하면

▶ 구현은 바뀌지만, 규칙은 안 바뀌기 때문

3. Infrastructure (외부 구현 계층)

여기서 실제 DB, API 연결을 담당한다.

1
2
3
4
5
6
7
public class UserRepository : IUserRepository
{
    public Task<User?> GetByIdAsync(Guid id)
    {
        // DB 조회 로직
    }
}

▶ 역할

EF Core 외부 API 호출 파일 / 캐시

▶ 전부 여기다 몰아넣는다

4. Presentation (API 계층)

사용자와 만나는 부분

1
2
3
4
5
6
7
8
9
10
11
[ApiController]
[Route("api/users")]
public class UserController : ControllerBase
{
    private readonly IUserService _userService;

    public UserController(IUserService userService)
    {
        _userService = userService;
    }
}

▶ 핵심

로직 없음 그냥 호출만 한다 왜 이렇게 나누냐?

한 문장으로 끝난다.

▶ “바뀌는 것과 안 바뀌는 것을 분리하기 위해서”

예를 들어

DB를 MSSQL → MySQL 변경 API 구조 변경 외부 서비스 교체

▶ Domain / Application은 그대로 유지 가능

이 구조의 장점

정리해보면 꽤 크다.

비즈니스 로직 보호 테스트 용이 유지보수 쉬움 팀 협업에 강함

특히

▶ “프로젝트가 커질수록 차이가 난다”

여기까지 상태

지금 상태는

▶ 구조만 잡혀있는 상태다

아직

DB 연결 없음 Service 없음 실제 동작 안 함 다음 편 예고

다음 편에서는

Service (UseCase) 구현 Repository 실제 연결 흐름 DTO / Command 구조

▶ 진짜 로직을 넣어본다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

© 곰이. 일부 권리 보유