Back-End (Web)/Spring

[Spring] Layered Architecture

JABHACK 2024. 12. 20. 15:44

Layered Architecture

📌 애플리케이션을 세 가지 주요 계층으로 나누어 구조화하는 방법으로 각 계층은 특정한 책임을 갖고 있으며, 계층 간에는 명확한 역할 분담이 이루어져 코드의 재사용성, 유지보수성, 확장성을 높이는 데 도움을 준다.

 

주요 특징

  1. 계층 분리:
    • 시스템을 기능별로 분리하여 모듈화.
  2. 책임 분리:
    • 각 계층은 고유한 책임과 역할을 가짐.
  3. 상호 의존성:
    • 상위 계층은 하위 계층에만 의존하며, 계층 간의 의존성을 제한.
  4. 유지보수 용이:
    • 특정 계층의 변경이 다른 계층에 최소한의 영향을 미침.

 

Layerd Architecture 개요 

 

  • 기존의 MVC 패턴에서 Controller는 역할이 무수히 많다.
    1. 요청에 대한 처리
    2. 예외처리
    3. View Template 응답 or Data 응답
    4. 비지니스 로직 처리
    5. DB 상호작용
  • 문제점
    • Controller에서 요청에 대한 모든 처리를 수행한다. 즉, 책임이 너무 많다.
    • 기능 추가, 수정, 삭제 등의 유지보수가 힘들어진다.
    • 코드의 재사용성이 떨어진다. 메서드로 분리하여도 메서드를 호출하는 중복 코드가 발생한다.

 

Layered Architecture 구조

 

  • Presentation Layer
    • 사용자의 요청을 받고 응답하는 역할을 수행한다.
    • 화면을 응답하거나 데이터를 응답하는 API를 정의한다.
  • Business Layer(Service Layer)
    • 비지니스 로직을 수행한다.
    • 요청을 해석하여 Repository Layer에 전달한다.
    • 일반적으로 하나의 비지니스 로직은 하나의 트랜잭션으로 동작한다.
  • Data Access Layer(Repository Layer)
    • 데이터베이스와 연동되어 실제 데이터를 관리한다.
  • 용어 설명
    • DTO(Data Transfer Object)
      • 계층간 데이터 전달을 위해 사용되는 객체이다.
    • Model
      • Entity
        • 추후 숙련주차에 배울 JPA와 관련이 있다.
        • JPA에서는 Entity라는 형태로 데이터를 반환한다.
    • DAO(Data Access Object)

 

계층 간 의존성

  • 상위 계층은 하위 계층에만 의존합니다.
  • 계층 간의 의존성을 단방향으로 제한하여 결합도를 낮춥니다.

계층 간 의존성 예시

Presentation → Application → Domain → Data Access

 

 

장점

  1. 유지보수성:
    • 계층별 역할이 분리되어, 특정 계층의 변경이 다른 계층에 미치는 영향을 최소화.
  2. 재사용성:
    • 서비스나 데이터 계층을 다른 애플리케이션에서도 재사용 가능.
  3. 테스트 용이성:
    • 계층 단위로 테스트가 가능하여, 단위 테스트와 통합 테스트를 쉽게 수행.
  4. 확장성:
    • 특정 계층에 새로운 기능을 추가하거나 변경하기 쉬움.

 

단점

  1. 복잡성 증가:
    • 계층 간의 통신 코드로 인해 초기 개발이 복잡해질 수 있음.
  2. 성능 문제:
    • 계층 간 호출이 많아지면 성능 저하 가능.
  3. 단순 CRUD에 과한 구조:
    • 작은 애플리케이션에서는 계층형 아키텍처가 불필요한 복잡성을 초래.

 

계층형 아키텍처를 사용할 때 적합한 경우

  1. 복잡한 비즈니스 로직:
    • 다양한 데이터 소스와 복잡한 비즈니스 규칙이 있는 애플리케이션.
  2. 협업 프로젝트:
    • 역할 분리가 명확해 팀 간 작업이 효율적.
  3. 대규모 애플리케이션:
    • 변경 사항을 쉽게 관리하고, 시스템 확장이 필요한 경우.

 

 

 

 

 

Layered Architecture 적용

 

 

 

1. Controller

  • 클라이언트의 요청을 받는 역할을 수행한다.
  • 요청에 대한 처리를 Service Layer에 전달한다.
  • Service에서 처리 완료된 결과를 클라이언트에 응답한다.
  • 사용하는 Annotation : @Controller, @RestController

2. Service

  • 사용자의 요청 사항을 처리한다.
  • DB와 상호작용이 필요한 경우, Repository Layer에게 요청한다.
  • 사용하는 Annotation: @Service

 

3. Repository

  • DB와 상호작용을 수행한다.
    • Connection 연결, 해제
    • CRUD 작업 처리
  • 사용하는 Annotation: @Repository

 

4. DTO(Data Transfer Object)

  • 계층간 데이터 전달을 위해 사용된다.
  • 요청 데이터를 처리하는 객체는 일반적으로 RequestDto로 명명한다.
  • 응답 데이터를 처리하는 객체는 일반적으로 ResponseDto로 명명한다.

 

DTO (Data Transfer Object)

📌 애플리케이션에서 계층 간 데이터 전송을 목적으로 사용하는 단순한 객체입니다.

  • 주로 데이터를 전송하기 위한 속성과 Getter/Setter 메서드만 포함하며, 비즈니스 로직을 포함하지 않습니다.

 

DTO의 주요 역할

  1. 데이터 캡슐화:
    • 데이터 구조를 명확히 정의하여 계층 간 데이터 교환을 표준화.
  2. 안전한 데이터 전달:
    • 데이터 모델(Entity)과 직접 연관되지 않아, 민감한 데이터 보호 가능.
  3. 데이터 변환 및 제한:
    • 클라이언트가 필요로 하는 데이터만 전달하도록 제한.
  4. API 설계에 유용:
    • RESTful API에서 응답(Response) 또는 요청(Request) 데이터를 정의.

 

 

DTO의 구조

DTO는 일반적으로 다음과 같은 형태로 작성됩니다:

  • 속성 필드
  • Getter/Setter 메서드
  • (선택적) 생성자, toString(), equals(), hashCode()
public class UserDTO {
    private Long id;
    private String name;
    private String email;

    // 기본 생성자
    public UserDTO() {}

    // 생성자
    public UserDTO(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getter와 Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

 

DTO의 사용 예

a. 요청(Request) DTO

클라이언트가 서버에 데이터를 보낼 때 사용.

Request DTO 예제

public class CreateUserRequest {
    private String name;
    private String email;

    // Getter와 Setter
}

 

Controller

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody CreateUserRequest request) {
        // 요청 데이터 처리
        return ResponseEntity.ok("User created: " + request.getName());
    }
}

 

요청 데이터

{
    "name": "John Doe",
    "email": "john.doe@example.com"
}

 

b. 응답(Response) DTO

서버가 클라이언트에 데이터를 반환할 때 사용.

 

Response DTO 예제

public class UserResponse {
    private Long id;
    private String name;
    private String email;

    public UserResponse(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getter만 제공
}

 

Controller

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
        // 서비스에서 가져온 데이터
        UserResponse response = new UserResponse(id, "John Doe", "john.doe@example.com");
        return ResponseEntity.ok(response);
    }
}

 

응답 데이터

{
    "id": 1,
    "name": "John Doe",
    "email": "john.doe@example.com"
}

 

Entity와 DTO의 차이

  Entity DTO
역할 데이터베이스 테이블과 직접 매핑. 데이터 전송용 객체.
책임 비즈니스 로직 및 데이터베이스 작업 포함. 단순히 데이터 전송 및 표현.
위치 Service, Repository 계층에서 사용. Controller와 Service 계층 간 전송.
직렬화 필요에 따라 가능. 주로 직렬화되어 전송(JSON, XML 등).
민감한 데이터 포함 가능. 민감한 데이터는 제외.

 

DTO 변환 (Entity ↔ DTO)

DTO는 Entity와 직접적으로 사용되지 않으며, 변환 과정을 거칩니다.

Service 계층에서 변환 예

@Service
public class UserService {

    public UserResponse getUserResponse(UserEntity entity) {
        return new UserResponse(
            entity.getId(),
            entity.getName(),
            entity.getEmail()
        );
    }
}

 

 

Spring Boot에서 DTO 자동 변환

Spring Boot에서 DTO 변환을 쉽게 하기 위해 ModelMapper 또는 MapStruct와 같은 라이브러리를 사용할 수 있습니다.

a. ModelMapper 예제

의존성 추가 (Maven)

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.1.0</version>
</dependency>

변환 코드

import org.modelmapper.ModelMapper;

@Service
public class UserService {

    private final ModelMapper modelMapper = new ModelMapper();

    public UserDTO convertToDTO(UserEntity entity) {
        return modelMapper.map(entity, UserDTO.class);
    }
}

 

DTO의 장점

  1. 데이터 보호:
    • 데이터베이스와 직접 연결된 Entity를 노출하지 않아 민감한 데이터 보호.
  2. 역할 분리:
    • Entity와 DTO를 분리하여 비즈니스 로직과 데이터 전송 책임을 명확히 구분.
  3. API 표준화:
    • 클라이언트 요청과 응답 형식을 명확히 정의 가능.
  4. 유지보수성 향상:
    • 데이터 전송 형식 변경이 Entity와 독립적으로 이루어짐.

 

결론

 

  • DTO(Data Transfer Object)는 계층 간 데이터를 주고받을 때 사용되는 단순한 객체입니다.
  • 데이터 캡슐화, 보안성 강화, API 설계 표준화 등의 장점으로, 특히 Spring MVC와 RESTful API 설계에서 널리 사용됩니다.
  • DTO를 활용하여 애플리케이션의 구조를 더 유연하고 유지보수 가능하게 설계할 수 있습니다.