[2025 백엔드] 도메인과 Model, 도메인 주도 개발 (DDD)

2025. 7. 22. 03:34·CS/백엔드

스프링 인 액션 책을 읽다가.. 도메인에 대해 나와서 좀 이해 좀 하고 넘어가려고 이 글을 쓴다.

 

 

도메인에 대한 이해가 없이는 코딩을 시작할 수는 없다. 요구사항을 분석한 것을 바탕으로 기획서, 유스케이스, 사용자 스토리를 통해 도메인을 이해하고 이를 바탕으로 도메인 모델 초안을 만들어야 비로소 코드를 작성할 수있다.

출처 : https://doing7.tistory.com/79

 

Domain, Entity, Value(Object)

Spring 프로젝트 중에 Domain, Entity, VO(value object)라는 용어들이 반복적으로 등장하지만, 정작 이들의 차이를 모르고 있다는 생각이들었다. 이에 관련되어 더 공부하고자 DDD START 도메인 주도 설계(최

doing7.tistory.com

그렇게 도메인이 중요한 개념이었다니...

내가 하는 개발은 DB우선개발, 또는 컨트롤러-서비스-레포지토리로 이루어진 3계층 아키텍처 구조였다.

그런데 도메인 구조는 높은 응집도로 복잡한 기능도 구현이 가능하다고한다.

 

프로그램이 복잡해질수록 도메인 구조가 유리한 편이며, 도메인 객체 단위로 테스트가 용이하다는 점이 있다.

지금 내가 프로그래밍하고 있는 것도 도메인 구조로 바꾸고 싶지만 그러면 엄청난 대공사가 될 게 뻔하기 때문에.. 우선은 간단한 CRUD 프로젝트는 3계층을 써도 된다고하니 우선은 3계층으로 가고 언젠가 시간이 좀 남으면 도메인을 씁시다..

 

도메인과 3계층 아키텍처를 섞어쓰는 것은 바람직하지 않다. 아니 그냥 안된다. 두 가지 아키텍처를 섞어쓰면 혼동하기 쉽기 때문. 

 

그럼 앞으로 복잡한 구조의 프로그래밍, 실력이 발달할 수록 필요한 구조인 도메인에 대해서 알아보도록 하자.

 

본 포스팅은 다음의 내용을 일부 참고하였으며, 다른 블로그를 참고할 경우 그 단락마다 링크를 기재합니다.

https://mangkyu.tistory.com/160

 

[Spring] 도메인 주도 설계 및 개발과 도메인 계층(도메인 객체 중심 개발)

이 내용은 토비의 스프링 1권의 797부터 시작하는 내용을 참고하며 작성하였습니다. 1. 도메인 주도 설계 및 개발(도메인 객체 중심 개발) [ 도메인 객체 중심 개발 ] 도메인 객체 중심 아키텍처란

mangkyu.tistory.com

 

 

ㅖ 사실 DDD 보자마자 DDD대왕 드립치고 싶긴했어요 (근데 내 원픽은 커비였음)

우선 알아봅시다


도메인이란?

 기존에 내가 쓰던 방식은 Controller - Service - DB로 이루어진 구조인데 (3계층 아키텍처), 도메인의 개념을 이제 이해해보고자 한다. 3계층 방식에서 도메인은 잘 쓰지 않았다. 기껏해야 DTO 대용으로? (굉장히 잘못 쓴 것 같긴하다)

도메인을 한 줄로 정리해보자면.. 비즈니스 로직과 이를 저장할 객체들을 담은 하나의 클래스? 정도로 이해해도 되지 않을까 싶다.

'도메인모델'이란 이 도메인을 반영한 객체를 말하고, DDD는 이를 중심으로 개발하는 것을 말한다.

 

이와 대비되는 개념으로는 DB중심 아키텍처가 있다. 도메인이 없고 서비스 계층에서 로직을 처리하는 방식. 3계층 아키텍처 - 무거은 서비스계층 - 가벼운 Entity 형식으로 주로 등장하는 편이다.

도메인을 공부하기 위해서 컨트롤러와 도메인만 있는 프로젝트를 만들어봤다.

간단한 기능으로 상점서비스를 운영한다고치고 만들어보기로 했다.

 

GPT에게 간단한 상황을 하나 제시해주라고 했더니 다음과 같은 상황을 제시했다.

package com.example.demo.domain;

import com.example.demo.dto.productDTO;

public class Book {
    private int id;
    private String name;
    private boolean availableToBorrow = true;

    public void borrow() {
        if (availableToBorrow) {
            System.out.println("대출되었습니다.");
            availableToBorrow = false;
        } else {
            System.out.println("대여 중입니다.");
        }
    }

    public void returnBook() {
        availableToBorrow = true;
    }
}

그냥 void로 간단하게 만들어본거

대충 도메인의 개념을 잡기위해 이렇게 만들었다.

확실히 기존에 비해 상태나 로직을 파악하기 쉬울 것 같다. 아 도메인 방식을 써볼까.. 좀 끌리는데....

 

기존에는 서비스가 ID나 name 등을 인자로 받아서 찾고 로직을 수행했지만, 서비스는 이제 인자를 받고, 레포지토리에서 찾고 도메인을 호출하는 일종의 중개자 역할을 맡게 되었다.

package com.example.demo.Service;

import com.example.demo.domain.Book;

public class BookService {
    public void BorrowBook(int BookId) {
        Book book = bookRepository.findById(BookId)
                .orElseThrow(() -> new RuntimeException("Book not found"));
        book.borrow();
        bookRepository.save(book); // 변경사항저장
    }

    public void ReturnBook(int BookId) {
        Book book = bookRepository.findById(BookId)
                .orElseThrow(() -> new RuntimeException("Book not found"));
        book.returnBook();
        bookRepository.save(book); // 변경사항 저장
    }
}

bookRepository는 안 만들어서 저 코드는 실제로 실행하지는 못한다.

저기에 이제 ResponseEntity를 반환하도록하면 될 듯하다. 하는김에 ResponseDTO도 수정해보기

참고로 저 코드에 bookRepository를 생성자로 넣어줘야한다.

 

만약 3계층 방식이었다면?

package com.example.demo.Service;

public class BookServiceIn3gyecheung {
    public void borrowBook(int bookId) {
        boolean availableToBorrow = true; //DB에서 찾아오기

        if (availableToBorrow) {
            System.out.println("대출되었습니다.");
            availableToBorrow = false;
            //DB에 찾아가서 False로 바꾸기
        } else {
            System.out.println("대여 중입니다.");
        }
    }
}

뭐 대충 이런 플로우??

DB에는 어떻게 접근하려나.. 

 

장점

1. 응집도 높이기

2. 불필요한 DI 줄이기

3. 서비스층 코드 간결화

 

궁금한거 1. -> 왜 서비스와 다르게 도메인에는 변수가 저장될까?

 GPT에게 물어본 결과 도메인은 특정 비즈니스 로직을 위해 설계된 객체고, 그렇기 때문에 도메인 안에 그 로직을 위한 변수와 메서드를 갖는다. 내가 하는 3계층아키텍쳐에서의 서비스는 (GPT 피셜로는 전통적이라고하더라) 상태 없는 함수 모음의 느낌이기에 로직만 있고 변수는 인자로 받아도 되지만, 도메인 방식은 로직(메서드)+상태(변수)를 담는 객체이기에 변수가 꼭 필요하다는 것이다. 서비스와 도메인의 변수 저장 여부가 다른 이유는 그 작동방식과 목적에서의 차이가 원인이라고 할 수 있겠다.

 

궁금한거2. -> 그럼 Book 객체는 어떻게 식별되나?

 새 Book 객체를 호출할 때마다 새 책이 저장되는 것은 알겠다만.. Book 객체를 식별하긴해야지 식별하지 않는다면 책 한 권 반납했는데 전체 책이 반납처리되고 그런 대참사는 방지해야하기 때문에.. GPT에게 물어보니 도메인에서는 그런 메서드를 처리할 필요가 없고 BookRepository.findById(bookId) 이런 메서드를 쓰면 저장된 특정한 Book 객체를 불러올 수 있다는데 아직 레포지토리 계층에 대해서 심도있게 다뤄보지는 않아서 그냥 그런기능이 있다만하고 넘어갔다.


DDD에서 DB에 접근하기 - Repository로 아주 쉬워지다!

 위에 DDD 실습코드에서 볼 수 있듯이 DDD는 Repository 사용이 권장된다. 거의 필수라고 봐야되나.. 기존 3계층 방식에서는 Repository를 써도되고 안써도 된다고한다.

 

bookRepository.save(book); // 변경사항저장

위에서 Repository에서 save 메서드를 book 객체를 DB에 저장하는데 사용한다.

그렇다면 save 메서드는 내가 직접 구현해서, 직접 DB에 접근해야할까? 원칙적으로는 그렇다.

 

근데 만약 Repository가 JpaRepository나 CrudRepository를 상속한다면?

.save 메서드가 기본적으로 지원된다. yml 파일에 DB를 이어주면 끝. DB에 대해서 고민만 했는데 이거 ERD만 잘 설계하면 되겠네 우선은

 

그리고 이 save 메서드에 대해 다른 분께서 쓰신 글이 있다.

https://velog.io/@kevin_/%EB%82%B4%EA%B0%80-repository.save%EB%A5%BC-%ED%86%B5%ED%95%B4%EC%84%9C-%EA%B0%9D%EC%B2%B4%EB%A5%BC-%EC%A0%80%EC%9E%A5%EC%8B%9C%EC%BC%B0%EB%8D%98-%EC%9D%B4%EC%9C%A0

 

내가 repository.save()를 통해서 객체를 저장시켰던 이유

그 때 당시에는 Spring은 물론 Java에 대한 개념도 매우 약할 때라 지금도 약하지만 > > > 레퍼런스의 코드를 가져다 사용하기에 급급했다. > > 당시의 나는 해당 개념에 대한 정확한 이해보다는 기

velog.io

 

 영속성에 대한 글인데.. GDG에서 개발기초스터디인가 뭐 그거 할 때 한 번 들은 기억이 나는 것 같다. flush니 영속이니 준영속이니 하던거.. 그거는 조만간 Repository에 대해서 다뤄볼 때 다룰까합니다. DB에 대해서도 공부해보공


관련 개념


Entity

@Entity
public class Book {
    @Id
    private int id;
    private String name;
    private boolean availableToBorrow = true;

@Entity

 JPA에서 사용하는 어노테이션으로, 선언시 JPA는 class 이름을 DB의 테이블의 이름과 대응시킨다. id, name, availableToBorrow는 테이블의 컬럼에 해당한다. Entity 어노테이션이 붙은 객체는 JPA가 관리 / 영속화하며, DB에 매핑되는 객체가 된다. 

 

 @Id 어노테이션은 테이블의 기본키로 인식되며 @Entity를 사용한 경우 필수적으로 들어가야한다. Entity 어노테이션의 식별자로 인식되며 이 식별자를 통해 각각의 Book 객체를 구분한다.


Value

@Embeddable
public class Address {
    private String city;
    private String street;
}

도메인이지만 Entity 어노테이션이 딱히 필요없는 경우도 있다. (위에는 GPT가 짜준 예시)

Entity와 반대되는 개념으로, Value의 경우에는 id(식별자) 없이 클래스를 구성하는 인자가 같을 경우 같은 객체로 다룰 수 있다. 즉, 식별자가 반드시 객체를 다른 객체와 구분짓는데 필요하다면 Entity고, 아니면 Value라고 할 수 있겠다.

 

위 코드는 주소 도메인으로 식별자에 관계없이 도시와 도로만 맞으면 같은 주소라고 할 수 있다. 위에서 Entity의 예시로 나온 Book의 경우에는 식별자가 같으면 작가와 제목이 달라도 같은 책으로 인식한다.

 

 Value의 경우에는 요소의 값이 바뀌면 다른 객체가 되므로 각 요소들은 불변객체로 만들어지며, 값은 setter는 없다.


DTO에서 쓰던 lombok 어노테이션을 여기서 써도 되나?

 그렇다. Getter는 써도되는데 Builder 어노테이션의 경우에는 Entity에 적용하려면 JPA 스타일 때문에 조심해야한다고한다. value의 경우에는 딱히 상관없다고. 


Model

사실 도메인을 조사하게된 계기인데.. 어쩌다보니 뒤로 밀렸다.

여기서 다루는 Model은 도메인 '모델'할 때의 모델이 아니라 스프링 MVC에서 사용하는 Model이다. Model이란 무엇일까?

 컨트롤러와 뷰 사이에서 데이터를 운반하는 객체라고 하는데.. Model에 있는 데이터는 뷰가 이해가능한 서블릿(Servlet) 데이터로 복사된다고 한다. addAttribute(키, 값)을 통해서 데이터를 세팅할 수 있다. 역시 JSON은 위대하다

 

암튼 어떻게 쓰냐면..

 기본적인 방식은 컨트롤러 인자에 Model 객체를 넣어주고.. Model 객체를 좀 이리저리 가꾼 다음에 String 형식으로 문자를 반환하면 끝. 그러면 반환된 문자열을 이름으로하는 html 파일을 찾아서 뷰로 보여준다. 

<p th:text="${message}"></p>

 뷰에서는 이렇게 처리하면 된다고.. message가 변수명이고, Model의 key 값을 저 중괄호 사이에 넣으면 value 값이 보여진다. html은 2년만이네..

 

암튼 th:text는 저렇게 key를 찾아 value를 보여주는 형식이고

th:each도 있는데 이는 리스트 같은 컬렉션을 일일히 반복문을 돌며 뷰에 보여줄 때 사용한다.


 

암튼 이러고 스프링 인 액션 책을 마저 읽는데.. html 파일을 해석하기가 왤케 힘들지 이러면안되는데

th:xx 이런 형식은 어떤 것을 나타내는지 이해하기가 쉽지 않다.

 

th:src="@{~~}" 기존 html의 속성인 src와 동일하나 경로를 스프링이 관리해준다.

th:object="${~~}" ~~이름을 가진 Object를 기준으로 삼는다. / form 에 이게 붙으면 해당 객체 type으로 form을 하겠다는 뜻

th:field="*{필드명}" 은 th:object를 기준으로 필드를 가리킨다. (필드는 클래스변수)

th:value"${~~}" 동적으로 변수 집어넣기 (변수명 ~~)

th:if="${조건}" 조건문

th:errors="*{필드명}"  에러시

 

th:if="${조건}" th:errors="*{필드면}" 을 div에 넣으면 자동으로 에러메시지를 표출해준다.

난 내가 직접 에러메시지 만들었어야했는데

 

그래도 th:~~ 형식을 이해하니까 html 파일이 조금 읽힌다.

CRUD를 하거나 할 땐 그냥 상태코드랑 메시지랑.. DTO 몇 개만 반환하면 됐었는데 이렇게 뷰를 작성하려하니 좀 어렵다.

 

올해 파이썬-자바-C(C++)을 배우는게 목표였는데

파이썬을 내년으로하고 JS를 배워볼까 생각 중이다. Node.js 에 관심이 생기기도하고..


이전에 알아본 것

없음

 

이후로 알아볼 것

- JPA

- DDD 관련 서적 찾아보기 ( 도메인 주도 개발 시작하기  | 한빛미디어)

 

졸려

인스타그램 데일리스크럼을 4시에 올리겠는데허허

'CS > 백엔드' 카테고리의 다른 글

[2025백엔드] 스프링인액션 독서 #5 - 3장. 데이터로 작업하기 #3  (2) 2025.08.04
[2025백엔드] 스프링인액션 독서 #4 - 3장.데이터로 작업하기 2  (2) 2025.08.01
[2025백엔드] 스프링인액션 독서 #3 - 3장.데이터로 작업하기 1  (3) 2025.07.29
[2025 백엔드] 스프링 인 액션 독서 #2 - 2장. 웹 애플리케이션 개발하기  (7) 2025.07.24
[2025 백엔드] 스프링 인 액션 독서 #1 - 1장. 스프링 시작하기  (2) 2025.07.21
'CS/백엔드' 카테고리의 다른 글
  • [2025백엔드] 스프링인액션 독서 #4 - 3장.데이터로 작업하기 2
  • [2025백엔드] 스프링인액션 독서 #3 - 3장.데이터로 작업하기 1
  • [2025 백엔드] 스프링 인 액션 독서 #2 - 2장. 웹 애플리케이션 개발하기
  • [2025 백엔드] 스프링 인 액션 독서 #1 - 1장. 스프링 시작하기
Radiata
Radiata
개발을 합니다.
  • Radiata
    DDD
    Radiata
  • 전체
    오늘
    어제
    • 분류 전체보기 (211)
      • 신년사 (3)
        • 2025년 (2)
        • 2026년 (1)
      • CS (59)
        • JVM (12)
        • 백엔드 (20)
        • 언어구현 (1)
        • 객체지향 (1)
        • 논리회로 (5)
        • 컴퓨터구조 (9)
        • 데이터베이스 (1)
        • 컴퓨터 네트워크 (10)
      • 언어공부 (64)
        • Java | Kotlin (48)
        • JavaScript | TypeScript (9)
        • C | C++ (6)
      • 개인 프로젝트 (11)
        • [2025] Happy2SendingMails (3)
        • [2026] 골든리포트! (8)
        • [2026] 순수자바로 개발하기 (0)
        • 기타 이것저것 (0)
      • 팀 프로젝트 (29)
        • [2025][GDG]홍대 맛집 아카이빙 프로젝트 (29)
      • 알고리즘 (13)
        • 백준풀이기록 (11)
      • 놀이터 (0)
      • 에러 수정일지 (2)
      • 고찰 (24)
        • CEOS 23기 회고록 (2)
  • 블로그 메뉴

    • CS
    • 언어공부
    • 개인 프로젝트
    • 팀 프로젝트
    • 알고리즘
    • 고찰
    • 신년사
    • 컬러잇 개발블로그
  • 링크

    • 컬러잇 개발블로그
  • 공지사항

  • 인기 글

  • 태그

    144
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Radiata
[2025 백엔드] 도메인과 Model, 도메인 주도 개발 (DDD)
상단으로

티스토리툴바