Architecture

레이어드 아키텍처(Layered Architecture)

TedDev 2025. 1. 12. 17:25
728x90

레이어드 아키텍처(Layered Architecture)는 소프트웨어 시스템을 계층(Layer)으로 나누어 설계하고 구현하는 소프트웨어 설계 패턴 중 하나이다. 이 아키텍처는 시스템을 여러 수준으로 나누고 각 계층이 특정한 역할과 책임을 맡도록 구성하여 코드의 유지보수성과 재사용성을 높이고 개발 프로세스를 단순화한다.

 

레이어드 아키텍처의 주요 계층

  1. 프레젠테이션 계층(Presentation Layer)
    • 사용자와의 상호작용을 담당하는 계층
    • UI(User Interface)를 포함하며 사용자가 데이터를 입력하거나 결과를 확인할 수 있도록 도와준다.
    • 일반적으로 HTML, CSS, JavaScript 또는 모바일 애플리케이션의 프론트엔드 부분이 포함된다.
    • 예: 웹 브라우저에서 렌더링되는 화면, 앱의 UI 구성 요소
  2. 애플리케이션 계층(Application Layer)
    • 비즈니스 로직을 처리하고 프레젠테이션 계층과 데이터 계층 사이에서 중재 역할을 한다.
    • 사용자의 요청을 해석하고 필요한 데이터를 가져오거나 저장하며 결과를 계산한 후 다시 프레젠테이션 계층으로 전달한다.
    • 서비스(Service) 계층으로도 불린다.
    • 예: 주문 생성, 사용자 인증, 워크플로 관리
  3. 도메인 계층(Domain Layer)
    • 비즈니스 규칙과 핵심 도메인 로직을 처리하는 계층이다.
    • 시스템의 핵심 동작을 정의하며 데이터베이스나 UI와 독립적으로 설계된다.
    • 도메인 객체나 엔터티(Entity), 도메인 서비스 등이 포함된다.
    • 예: 비즈니스 규칙이 반영된 알고리즘, 데이터 유효성 검사
  4. 데이터 계층(Data Layer)
    • 데이터베이스와 상호작용하는 계층으로 데이터의 영속성과 저장을 담당한다.
    • SQL 쿼리, ORM(Object-Relational Mapping), 데이터 접근 레포지토리 등이 여기에 포함된다.
    • 예: MySQL, MongoDB, 데이터 CRUD 연산

 

레이어드 아키텍처의 특징

  1. 모듈화(Modularity) : 각 계층이 독립적으로 설계되므로 특정 계층의 변경이 다른 계층에 영향을 최소화한다.
  2. 유지보수성(Maintainability) : 계층 간의 역할이 명확하게 구분되어 있어 코드의 가독성과 유지보수가 용이하다.
  3. 재사용성(Reusability) : 도메인 계층과 같은 공통 기능은 여러 애플리케이션에서 재사용할 수 있다.
  4. 테스트 용이성(Testability) : 계층별로 독립적인 단위 테스트가 가능하다.

 

레이어드 아키텍처의 장점

  • 코드 구조가 명확하고 조직적이어서 팀 개발에 유리하다.
  • 비즈니스 로직과 데이터 접근 로직을 분리해 관심사를 분리(Separation of Concerns)한다.
  • 각 계층을 독립적으로 개발하거나 교체할 수 있습니다.

레이어드 아키텍처의 단점

  • 계층 간의 통신 오버헤드가 발생할 수 있다.
  • 작은 프로젝트에서는 과도한 복잡성을 초래할 수 있다.
  • 엄격한 계층 구조를 유지하려면 개발자가 아키텍처 원칙을 잘 준수해야 한다.

 


 

레이어드 아키텍처는 DIP(Dependency Inversion Principle)OCP(Open-Closed Principle)의 원칙에 기반하여 설계되며 이를 통해 코드의 유연성과 확장성을 확보한다.

 

1. DIP (Dependency Inversion Principle)

DIP 원칙

  • 고수준 모듈(비즈니스 로직 등)은 저수준 모듈(데이터 접근, 특정 기술 등)에 의존하지 말고 둘 다 추상화에 의존해야 한다.
  • 추상화는 구체적인 구현 세부 사항보다 우선시되어야 한다.

레이어드 아키텍처에서 DIP의 적용

레이어드 아키텍처는 계층 간의 의존성을 "구체적 구현"이 아닌 "추상화된 인터페이스"를 통해 관리한다.

  • 애플리케이션 계층 ↔ 데이터 계층
    데이터 계층에서 사용하는 특정 데이터베이스(MySQL, MongoDB 등)에 종속되지 않도록 데이터 접근 인터페이스(Repository 패턴 등)를 사용합니다.
    예: UserRepository라는 인터페이스를 정의하고 이를 구현한 MySQLUserRepository, MongoDBUserRepository 같은 구체적 클래스가 존재한다.
  • 프레젠테이션 계층 ↔ 애플리케이션 계층
    프레젠테이션 계층은 애플리케이션 계층의 구체적 구현에 의존하지 않고 서비스 인터페이스를 통해 작업을 요청합니다.
    예: UserService라는 인터페이스를 사용해 그 구현체가 무엇이든 프레젠테이션 계층은 알 필요가 없다.

DIP의 장점

  • 계층 간의 의존성이 느슨하게 결합되어 구현체를 쉽게 교체하거나 확장할 수 있다.
  • 테스트 시 실제 구현 대신 Mock 객체를 주입하여 단위 테스트를 수행할 수 있다.

 

2. OCP (Open-Closed Principle)

OCP 원칙

  • 소프트웨어 엔터티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고 수정에는 닫혀 있어야 한다.

레이어드 아키텍처에서 OCP의 적용

레이어드 아키텍처는 계층 간의 의존성을 인터페이스나 추상 클래스를 통해 관리하며 새로운 요구 사항이 발생했을 때 기존 코드를 수정하지 않고 새로운 구현체를 추가하는 방식으로 확장한다.

  • 데이터 계층의 확장
    특정 데이터베이스(MySQL)를 사용하던 시스템에서 새로운 데이터 소스(MongoDB)를 추가해야 한다면, 기존 Repository 인터페이스를 수정하지 않고, 새로운 MongoDBUserRepository 클래스를 구현하여 기존 시스템에 통합할 수 있다.
  • 서비스 계층의 확장
    새로운 비즈니스 로직이 추가될 경우 기존 서비스 클래스를 수정하지 않고 인터페이스를 구현한 새로운 클래스를 추가하여 기존 시스템을 확장할 수 있다.

OCP의 장점

  • 코드 수정 없이 새로운 기능을 쉽게 추가할 수 있어 시스템이 확장 가능해진다.
  • 기존 코드의 안정성이 보장되어 유지보수가 용이해진다.

 

DIP와 OCP가 레이어드 아키텍처에서 동작하는 구조

  1. DIP를 만족하기 위한 설계
    • 각 계층은 하위 계층의 구체적 구현이 아니라 인터페이스나 추상 클래스에 의존한다.
    • 예: 애플리케이션 계층은 UserRepository라는 인터페이스에 의존하며 데이터 계층의 구현체는 해당 인터페이스를 구현한다.
  2. OCP를 만족하기 위한 설계
    • 인터페이스 기반으로 설계되었기 때문에 새로운 데이터 소스, 비즈니스 로직, 프레젠테이션 로직을 추가할 때 기존 코드를 변경하지 않고 확장할 수 있다.
// 1. 인터페이스 정의 (DIP 적용)
public interface UserRepository {
    User getUserById(int userId);
}

// 2. 데이터 계층 구현체 (DIP, OCP 적용)
class MySQLUserRepository implements UserRepository {
    @Override
    public User getUserById(int userId) {
        System.out.println("Fetching user from MySQL database");
        return new User(userId, "MySQL User");
    }
}

class MongoDBUserRepository implements UserRepository {
    @Override
    public User getUserById(int userId) {
        System.out.println("Fetching user from MongoDB database");
        return new User(userId, "MongoDB User");
    }
}

// 3. User 클래스 정의
class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "'}";
    }
}

// 4. 애플리케이션 계층
class UserService {
    private final UserRepository repository;

    public UserService(UserRepository repository) {
        this.repository = repository;
    }

    public User getUser(int userId) {
        return repository.getUserById(userId);
    }
}

// 5. 프레젠테이션 계층
public class Main {
    public static void main(String[] args) {
        // MySQL 저장소 사용
        UserRepository mysqlRepo = new MySQLUserRepository();
        UserService mysqlService = new UserService(mysqlRepo);
        System.out.println(mysqlService.getUser(1));

        // MongoDB 저장소로 교체
        UserRepository mongoRepo = new MongoDBUserRepository();
        UserService mongoService = new UserService(mongoRepo);
        System.out.println(mongoService.getUser(2));
    }
}
 

코드 설명

  1. UserRepository 인터페이스
    • 데이터 계층의 추상화를 제공한다.
    • getUserById 메서드를 정의하여 구현체가 각 데이터베이스에 맞는 동작을 구현하도록 한다.
  2. 데이터 계층 구현체
    • MySQLUserRepository와 MongoDBUserRepository는 UserRepository 인터페이스를 구현한다.
    • 각각 MySQL과 MongoDB에 맞는 데이터베이스 동작을 제공한다.
  3. UserService 클래스
    • 애플리케이션 계층의 역할을 수행하며 UserRepository 인터페이스를 통해 데이터 계층과 통신한다.
    • 구체적인 데이터 저장소 구현체에 의존하지 않고 인터페이스만 사용한다.
  4. Main 클래스:
    • 프레젠테이션 계층에서 UserService를 사용하여 데이터를 가져온다.
    • 데이터 계층 구현체를 교체(MySQLUserRepository → MongoDBUserRepository)할 때 코드 변경 없이 확장이 가능하다.

DIP와 OCP 적용 확인

  1. DIP
    • UserService는 UserRepository 인터페이스에 의존하며 구체적인 구현체(MySQL, MongoDB)에 의존하지 않는다.
  2. OCP
    • 새로운 데이터 저장소가 필요할 경우 UserRepository를 구현한 새로운 클래스를 추가하기만 하면 된다.
    • 기존 코드는 수정할 필요가 없다.
반응형