SpringLayeredArchitecture

Spring 애플리케이션은 일반적으로 계층형 아키텍처로 구성됩니다. 이 문서에서는 각 계층의 역할과 효과적인 테스트 방법에 대해 알아봅니다.

1. 도메인(Domain) 🧩

  • 엔티티 객체

    • 예: id, name과 같이 실제 데이터베이스에 저장될 필드를 보유.

    • 애플리케이션의 핵심 비즈니스 데이터를 표현하며, 보통 Getter/Setter 또는 Lombok을 통해 필드를 접근.

2. 리포지토리(Repository) 📦

  • 인터페이스 기반 설계

    • 예: save(), findById(), findByName(), findAll() 등과 같은 메서드를 추상화.

    • 추후 관계형 DB, NoSQL, 외부 API 등 다양한 저장소 구현체로 교체가 가능하도록 인터페이스로 설계하는 것이 핵심.

  • 메모리 기반 구현체

    • 실무 전환 전, 학습이나 빠른 프로토타입 용도로 간단히 Map 등을 사용해 데이터를 임시 저장.

    • 동시성 이슈는 고려하지 않았으며, 예제나 테스트 시 편리하게 활용.

3. 테스트(Test)와 "Given-When-Then" 구조 🧪

테스트 구조

  1. 셋업(Setup)

    • 각 테스트(메서드) 실행 전, 필요한 객체나 데이터를 준비하는 단계.

    • JUnit에서 @BeforeEach 어노테이션을 사용하면 각 테스트마다 독립적인 초기화를 할 수 있음.

  2. 클리어링(Clearing)

    • 테스트가 끝난 후 환경을 원상태로 복구.

    • JUnit에서 @AfterEach를 통해 인메모리 저장소를 비우는 등 테스트 간 간섭을 방지.

Given-When-Then (BDD 스타일)

  • Given(전제/상황) 📋

    • 테스트에 필요한 입력 데이터나 상태 준비.

  • When(행동) ▶️

    • 테스트하고자 하는 실제 동작을 호출/실행.

  • Then(검증)

    • 결과가 예상대로 나왔는지 확인(Assertion).

이러한 구조를 사용하면 테스트 시나리오가 명확해지고, 무엇을 테스트하고 어떤 결과를 기대하는지 한눈에 파악하기 쉬워집니다.

4. 서비스(Service)와 DI(Dependency Injection) 🔄

  • 서비스 계층

    • 실제 비즈니스 로직이 위치하며, 필요 시 중복 검증이나 예외 처리 등의 핵심 로직을 담당.

    • 컨트롤러(또는 API)가 서비스 계층을 호출해 비즈니스를 수행.

  • 의존성 주입(DI)

    • 스프링에서는 보통 생성자를 통해 리포지토리 인터페이스를 주입(Constructor Injection).

    • 다른 구현체로 손쉽게 교체해도 서비스 코드는 수정 없이 재사용 가능.

정리 📝

  1. 도메인: 핵심 데이터(엔티티) 구조를 정의.

  2. 리포지토리: 데이터 접근 계층. 인터페이스-구현체 구조로 설계해 유연성 확보.

  3. 테스트:

    • Given-When-Then 형태로 직관적인 시나리오 작성.

    • @BeforeEach@AfterEach를 통해 테스트 독립성 보장.

  4. 서비스 & DI:

    • 비즈니스 로직을 담당하는 서비스 계층.

    • 인터페이스 기반 리포지토리를 생성자 주입 받아 사용.

이러한 구조를 통해 유연한 계층 분리, 테스트 용이성, 유지보수성을 모두 확보할 수 있습니다. 특히 Given-When-Then 방식을 활용하면 테스트 시나리오가 명확해져, 예제 프로젝트뿐 아니라 실무 환경에서도 효과적인 검증 방식을 마련할 수 있습니다.

Last updated