Clean Architecture 정리 - 5부 21장 소리치는 아키텍처
2025. 7. 24. 15:34

소프트웨어 어플리케이션의 아키텍처는 유스케이스가 중심이 되어야 한다.

 

프레임워크(Spring, React 등)는 껍데기에 불과하다.

아키텍처의 중심이 되어서는 안 된다.


아키텍처의 목적

좋은 아키텍처는 프레임워크, 데이터베이스, 웹 서버 같은 외부 요소에 대한 결정을 늦출 수 있게 한다.

프레임워크는 언제든 교체 가능한 열린 선택지여야 한다.

이런 아키텍처는 프로젝트 후반까지도 Rails, Spring, Hibernate, MySQL 같은 도구에 대한 결정을 유보할 수 있으며, 필요할 경우 쉽게 변경 가능해야 한다.

즉, 유스케이스에 집중하고 지엽적인 관심사(입출력, 저장 등)는 분리해야 한다.


웹은 아키텍처의 일부일까?

웹은 단지 사용자와의 입출력을 처리하는 계층일 뿐이다.

결국, 외부 요소이다.


프레임워크는 도구일 뿐이다

프레임워크는 강력한 도구지만 시스템 전체를 지배하게 해서는 안 된다.

 

이를 위해:

  • 프레임워크 없이도 로직을 구성할 수 있는가?
  • 프레임워크 중심이 아닌 유스케이스 중심 아키텍처를 설계했는가?

스스로 질문해야 한다.


테스트하기 쉬운 아키텍처

아키텍처가 유스케이스 중심이라면, 프레임워크가 준비되지 않아도 단위 테스트가 가능해야 한다.

웹 서버, 데이터베이스 없이도 핵심 로직은 테스트되어야 한다.

  • 엔티티는 단순하고 독립적이어야 한다
  • 프레임워크나 DB에 의존하지 않아야 한다
  • 유스케이스는 엔티티를 조작할 수 있어야 한다
  • 전체 유스케이스는 프레임워크 없이도 테스트 가능해야 한다.

프론트엔드에서의 유스케이스 중심 아키텍처

1. 엔티티

export class Post {
  constructor(
    public title: string,
    public content: string
  ) {}

  validate() {
    if (this.title.length < 3) {
      throw new Error('제목은 3자 이상이어야 합니다.');
    }
  }
}

2. 유스케이스

import { Post } from './Post';

export interface CreatePostResponse {
  success: boolean;
  data: {
    title: string;
    content: string;
  };
}

export class CreatePostUseCase {
  execute(title: string, content: string): CreatePostResponse {
    const post = new Post(title, content);
    post.validate();

    // 저장은 외부에서 수행하도록 유스케이스는 처리된 결과만 반환
    return {
      success: true,
      data: {
        title: post.title,
        content: post.content
      }
    };
  }
}

2-1. 유스케이스 단위 테스트 예시

import { CreatePostUseCase } from './CreatePostUseCase';
import { Post } from './Post';

test('제목이 3자 미만이면 에러가 발생해야 한다', () => {
  const useCase = new CreatePostUseCase();

  expect(() => {
    useCase.execute('hi', '내용');
  }).toThrow('제목은 3자 이상이어야 합니다.');
});

test('유효한 포스트는 결과 객체로 반환된다', () => {
  const useCase = new CreatePostUseCase();

  const result = useCase.execute('정상 제목', '내용');

  expect(result.success).toBe(true);
  expect(result.data.title).toBe('정상 제목');
});

3. 프레임워크 레이어 (React)

const PostForm = () => {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const useCase = new CreatePostUseCase();

  const onSubmit = () => {
    try {
      const result = useCase.execute(title, content);
      // 저장은 여기서 수행
      postRepository.save({ title: result.data.title, content: result.data.content });
    } catch (e) {
      alert(e.message);
    }
  };

  return (
    <form>
      <input value={title} onChange={e => setTitle(e.target.value)} />
      <textarea value={content} onChange={e => setContent(e.target.value)} />
      <button type="button" onClick={onSubmit}>등록</button>
    </form>
  );
};


※ 본 글은 『Clean Architecture』(로버트 C. 마틴 저) 5부 21장을 기반으로 학습 목적으로 요약한 글입니다.

※ 이 글은 책의 내용을 요약한 것으로, 원문 없이 읽을 경우 오해의 여지가 있을 수 있습니다. 정확한 이해를 위해 원서의 정독을 권장합니다.