brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Aug 03. 2019

웹서비스 백엔드 애플리케이션 아키텍처(1)-클린아키텍처

1. 클린 아키텍처 적용한 스프링 기반 애플리케이션 구축


제 글을 읽지 마세요!


최근에 나온
"만들면서 배우는 클린 아키텍처" 라는 책을 읽어보시길 바랍니다. ^^



http://www.yes24.com/Product/Goods/105138479

 







"웹서비스 백엔드 애플리케이션 아키텍처"라는 주제로 글을 작성해서 공유합니다. 아키텍처에 대한 필자의 지극히 개인적인 생각을 정리하였습니다. 댓글로 피드백 해주시면 서로 배워가는 좋은 기회가 될 것 같습니다.



"웹서비스 백엔드 애플리케이션 아키텍처"


목차

1. 클린 아키텍처를 적용한 스프링 기반 애플리케이션 구축(이번 글)

2. 대용량 트래픽을 위한 백엔드 애플리케이션 캐싱 전략

3. 미정



글을 작성하면서


"클린 아키텍처"는 "로버트 C 마틴"의 소프트웨어 아키텍처 이론이다. 작년에 필자가 해당 원서를 읽고 서평을 작성했었다. 필자의 서평을 한번 읽어보고, 관심이 있는 개발자는 원서를 구매해서 보기를 추천한다.  

https://brunch.co.kr/@springboot/130


이 글은, 클린 아키텍처 이론을 참고해서, 웹서비스 백엔드 애플리케이션 아키텍처에 대해서 이야기하는 글이다. 하지만, 필자가 영어 실력이 많이 부족해서, 원서의 내용을 100% 이해하지 못했고, 원서 내용을 필자의 시각으로 재해석했다. 필자의 이 글은, 평범한 개발자의 개인 의견일 뿐이다. 이 글에서 설명하는 내용이 "로버트 C. 마틴"이 주장하는 아키텍처 이론과 100% 일치하지 않을 수 있다는 점을 이해해주기를 바란다. 아키텍처에는 정답이 없다. 비즈니스 도메인 특성에 맞는 가장 적합한 아키텍처를 구축하는 것이 중요하다. 특정 아키텍처가 항상 정답이다 라고 말하고 싶지는 않고, 클린 아키텍처 역시 항상 정답이 아니라는 것을 생각하고, 이 글을 읽어주기를 바란다.


유명한 소프트웨어 엔지니어인 "켄트 벡"은 그의 저서 "켄트벡의 구현 패턴"에서 아래와 같은 명언을 남겼다.


아무리 장황하게 많은 패턴을 늘어놓더라도, 프로그래밍을 하면서 발생하는 모든 상황을 커버할 수 없다.
- "켄트 벡"



이 글을 쓰게 된 계기


필자는, "클린 아키텍처" 원서를 읽으면서, 필자가 그동안 경험해왔던 기술에 적용할 수 있을지에 대해서 생각을 해봤다. 예를 들어서, JPA 를 사용하는 스프링 애플리케이션에서 클린 아키텍처를 적용할 수 있을까 하는 생각이다. 이 내용에 대해서는 필자의 글 후반에 다시 작성하겠다.



"Software Architecture is The Art Of Drawing Lines That I Call Boundaries. - 로버트C. 마틴"


"소프트웨어 아키텍처는 경계라는 선을 그리는 예술이다. 이러한 경계들은 소프트웨어 요소들을 서로 분리하고 디펜던시 의존성을 제한한다. 아키텍트의 목표는 필요한 시스템을 구축하고 유지하는 데 필요한 인적 리소스를 최소화하는 것이다. 예를 들어서, 비즈니스 유스케이스와 데이터베이스 사이의 경계선을 그릴 수 있다. 그 선은 비즈니스 규칙이 데이터베이스에 대해 전혀 알지 못하도록 막았다. 그 결정은 데이터베이스의 선택과 실행을 뒤로 늦출 수 있었고, 데이터베이스에 의존한 문제가 발생하지 않았다. 중요한 것과 중요하지 않은 것 사이에 선을 긋는다. UI는 비즈니스 규칙에 영향을 미치지 않아야 하고, 데이터베이스는 UI에 영향을 미치지 않아야 한다. 물론, 대부분의 우리들은 데이터베이스는 비즈니스 규칙과 불가피하게 연결되어 있다고 믿고 있다. 하지만, 데이터베이스는 비즈니스 규칙이 간접적으로 사용할 수 있는 도구일 뿐이다. 그래서, 우리는 인터페이스 뒤에 데이터베이스를 놓을 수 있도록 설계를 해야 한다. 실제로 소프트웨어 개발, 기술의 역사는 확장 가능하고 유지 관리 가능한 시스템 아키텍처를 구축하기 위해 플러그인을 만드는 방법에 관한 이야기이다. 핵심 비즈니스 규칙은 다른 컴포넌트들과 독립적으로 유지된다.  - 로버트 C. 마틴"


"로버트 C.마틴" 의 "클린 아키텍처"에서 가장 중요하게 강조하는 점은 바로, 디펜던시 의존성이다. 이 글은, 클린 아키텍처의 디펜던시 의존성에 대해서, 필자의 샘플 예시를 바탕으로 설명하겠다.  



시스템 구성도 및 기본 설계


간단한 백엔드 API 서버를 구축할 것이다. API 요구사항은 아래와 같다.

블로그 검색 결과를 Rest API 로 제공한다.

블로그 데이터 조회는 오픈 API 를 사용한다.

사용자의 Request 가 있을 때마다 오픈 API 를 바로 호출하지 않고, 미리 저장해놓은 Cache 저장소에서 데이터를 가져온다.

어드민에서 주기적으로 Message 를 API 에 전송해서, 오픈API 결과를 캐싱하도록 요청한다.

해당 샘플은, 하루만에 구축할 수 있는 아주 간단한 시스템이다. 보통 무료 오픈 API 는 요청 콜 수 제한이 있다. 물론, 유료 계정을 사용하면 요청콜수 제한 없이 사용할 수는 있지만, 필자의 샘플에서는 무료 오픈 API 를 사용한다는 가정이다. 그래서, 데이터를 캐싱할 것이고, 사용자의 요청이 있을때는 캐싱 데이터를 제공할 것이다. 프론트에서 API 서버를 호출하면, 아래와 같이 블로그 정보를 JSON 의 형태로 제공할 것이다.

프론트에서는 전달받은 JSON 으로 화면에 렌더링을 할 것이다. 필자의 이 글에서는, 백엔드 중심으로 작성할 예정으로, 프론트에 대한 내용은 상세하게 서술하지 않겠다. 어쨋든, 캐싱 데이터는 레디스에 저장되고, 아래와 같이 직렬화되어서 저장이 될 것이다.

레디스에 저장되는 키는 "blog:articles:검색쿼리" 규칙으로 설계하였다. 만약 사용자가 "스프링부트" 라는 검색어로 블로그 정보를 조회한다면, 레디스에 저장되어 있는 "blog:articles:스프링부트" 라는 키 값을 조회해서 제공할 것이다. 물론, 위에서도 설명했지만 "스프링부트" 라는 검색어에 대한 데이터를 미리 캐싱처리해서 저장해놓을 것이다.



클린 아키텍처, 디펜던시 의존성


"클린 아키텍처" 이론의 디펜던시 의존성에 대해서 알아보자.

"<클린 아키텍처> 의 동심원은 소프트웨어의 각각 다른 레이어 영역을 나타낸다. 내부 원은 비즈니스 정책이다. 클린 아키텍처에서 가장 중요한 개념은 디펜던시(의존성) 규칙이다. 소스 코드 의존성은 내부에서만 상위 수준 정책을 가리키도록 해야 한다. 내부 원 안에 있는 어떤 것도 외부 원의 무언가에 대해서 전혀 알 수 없다. 외부 원에 있는 어떤 것도 내부 원들에 영향을 미치기를 원하지 않는다. Entities는 전사적으로 중요한 비즈니스 규칙을 요약한다. Entities 는 메서드를 포함하는 객체일 수도 있고, 데이터 구조일 수도 있다. Entities 는 가장 일반적이고 높은 수준의 규칙을 요약한다. 외부적인 무언가가 바뀌면 가장 덜 변해야 한다. UseCase는 애플리케이션별 비즈니스 규칙을 포함한다. 시스템의 모든 사용 사례를 캡슐화하고 구현한다. 이러한 사용 사례는 기업 간 데이터 흐름을 조정한다. UseCase 계층은 데이터베이스, UI 또는 어떤 공통 프레임워크 등 외부에 영향을 받을 것으로 기대하지 않으며, 외부 영역으로부터 격리되어야 한다. InterFace Adapter 계층은 컨트롤러와, 모델, 뷰 등 MVC 아키텍처를 포함하는 계층이다. 마지막으로 가장 바깥쪽 계층인, Framework & Drivers 영역은 일반적으로 데이터베이스 및 웹 프레임워크와 같은 도구로 구성된다. 사실, 이 네 개 영역 이상의 것이 필요할 수 있다. 그러나, 디펜던시 규칙은 항상 적용이 된다. 소스 디펜던시는 항상 안쪽을 가리키며, 내부로 이동함에 따라 추상화 수준이 높아진다. 이러한 규칙을 준수하는 것은 어렵지 않으며, 앞으로 많은 고민들을 해결해 줄 것이다. 소프트웨어를 계층으로 분리하고, 디펜던시 규칙을 준수함으로써, 데이터베이스나 웹 프레임워크와 같은 시스템이 외부 부분들이 쓸모없게 될 때, 그러한 쓸모없는 요소들을 최소한의 작업으로 대체할 수 있을 것이다.   - 로버트 C. 마틴"


필자는, 클린 아키텍처의 디펜던시 의존성을 아래와 같이 재해석 하였다.  

Entity, Domain : 엔티티 정의

UseCase : 비즈니스 규칙 정의

Service, Repository : 비즈니스 규칙 정의, UseCase 의 구체화

Controller : 애플리케이션 API 엔드포인트

Data Providers : UseCase 또는 Service, Repository 의 구현체


필자의 필력이 떨어지므로, 자세한 설명은 생략하고, 필자의 샘플 코드를 같이 보면서 설명하겠다. 패키지 구조는 아래와 같다.

패키지 레이어는 config, core, entry, provider 으로 구분된다. core 패키지가 가장 추상화 수준이 높은, 동심원에서 가장 안쪽의 영역이다. 참고로, 일반적으로 추상화수준이 높은 core 영역은 멀티모듈 또는 서브모듈로 구성하는 것도 가능하다. 이 글에서는, 일단, 단일 애플리케이션이라는 가정하게 작성하는 글이다. 멀티 모듈에 대해서 심도있게 알고 싶은 개발자는 아래 글을 읽어보길 바란다.

http://woowabros.github.io/study/2019/07/01/multi-module.html

필자가 구성한 샘플 코드의 다이어그램은 아래와 같다.


이미지가 선명하게 안보이는데, 브런치 이미지 업로드가 잘 안되는 경우가 있다. 필자에게는 아무런 이득이 없는 브런치를 때려치고, 하루빨리 미디엄 또는 괜찮은 블로그 서비스로 갈아타든가 해야할 것 같다. 주말에 취미도 없이 이렇게 집에서 글이나 쓰고 있고, 읽어주는 사람도 거의 없을텐데 수익은 1도 없고 회의감이 밀려온다.


하지만, 정신차리고 이번 글은 일단 잘 마무리해보자.



Core(UseCase, Entity, Domain, Service, Repository)


Core 패키지에는, 동심원에서 가장 안쪽 영역의 클래스와 인터페이스를 포함한다. 동심원의 가장 안쪽에 존재하기 때문에 추상화 수준은 가장 높다. Entity, Domain 패키지에는 기본적인 도메인 모델을 정의한다. 필자의 샘플 애플리케이션에서는 아래와 같이 Blog 라는 도메인 클래스를 정의하였다.

Blog 클래스는 오픈 API 를 통해서 가져온 데이터를 매핑할 것이다. 사실, DTO 클래스를 별도로 정의하는게 좋을 수도 있다. 일단, 이 글에서는 아주 간단하게 해당 클래스에 매핑할 예정이다. UseCase 패키지에는 BlogUseCase 라는 인터페이스를 정의한다. 두개의 메서드를 아래와 같이 정의하였다.


findBlogByQuery : 검색 쿼리에 맞는 블로그 정보를 조회한다.

updateBlogByQuery : 검색 쿼리에 맞는 블로그 정보를 업데이트 해서 캐시 저장소에 저장한다.


참고로, 리턴 타입이 Mono 이다. Mono 는 "Reactor" 에서 제공하는 Publisher 구현체이다. 스프링 웹플럭스에서는 "Reactor" 라이브러리를 사용하고, 필자의 이 글에서는 스프링 웹플럭스 기반으로 작성하였다. 리액티브 프로그래밍에 관심이 있는 개발자는 필자의 글을 읽어보길 바란다.

https://brunch.co.kr/magazine/reactor


BlogUseCase 인터페이스를 BlogService 라는 클래스에서 구현한다.



Controller --> UseCase


Controller(컨트롤러)는 애플리케이션의 엔드포인트이다. 프론트에서는 Controller 의 엔드포인트에 Request 를 한다. 필자의 컨트롤러에서는 BlogUseCase 인터페이스를 호출하는데, 아래 화면과 같이 인터페이스의 구현체를 직접 호출하지는 않는다.

컨트롤러에서는, 구현체를 직접 호출하지 않고 아래와 같이 인터페이스를 호출한다.

샘플 코드는 아래와 같다.


필자의 샘플 코드에서는 Controller 에서 UseCase 를 바로 호출했지만, Controller 에서 Service 를 호출해도 크게 상관은 없다. 이 글에서 강조하고 싶은 점은, Controller 는 UseCase 또는 Service 에 대해서 잘 알고 있어야 하지만, 반대로 UseCase 또는 Service 는 Controller 에 대해서 알지 못해야 한다는 사실이다. 필자의 동심원 그림을 다시 보자.


동심원의 안으로 갈수록 추상화 수준은 높아진다. 원 안쪽에 위치하는 UseCase, Service 는 자주 변경되지 않는다. 그보다 더 안쪽에 있는 Entity, Domain 는 거의 변경되지 않을 것이다. 원 안쪽에서의 변화가 원 바깥쪽에 영향을 미치는 것은 자연스러운 일이다. 즉, Controller 는 UseCase,Service 에 대해서 잘 알고 있기 때문에, UseCase 또는 Service 의 변경이 되면 Controller 도 변경을 해야한다. 하지만, 반대로 원 바깥에서의 변화는 원 안쪽에 영향을 주면 안된다. 즉, UseCase 또는 Service 는 Controller 에 대해서 잘 모르기 때문에, Controller 에서의 변화는 UseCase 에 영향을 주면 안된다. 이것이 바로 로버트 C. 마틴 의 "클린 아키텍처" 이론의 디펜던시 룰 이다.


혹시, 이해가 잘 되는가...?? 디펜던시 의존성에 대해서, 필자는 "안다,모른다" 로 표현하였다. 어쨋든, 이 개념을 잘 이해를 해야 필자의 재미없고, 지루한 이 글을 계속 읽을 수 있다.



Listener --> UseCase


Listener 는 Controller 과 동일한 영역이라고 생각하면 된다. 어쨋든, Listener 역시 엔드포인트이고, 메시지를 전달받는 끝점이다. 필자의 샘플 코드에서는, 블로그 검색 결과를 캐싱에 대한 업데이트 신호를 수신하게 된다. Listener 에서 UseCase 를 사용하는 방법은, 컨트롤러에서의 UseCase 호출 방식과 유사하다. BlogService 구현체를 직접 호출하지 않는다. 인터페이스를 호출할 것이다.

자세한 설명은 생략한다.

사실, 여기까지는 아주 중요한 내용은 아니다.



이제부터 집중해서 보자. 글 내용이 조금씩 어려워진다.  


UseCase --> UseCase (1)


필자의 애플리케이션에서는, 오픈 API 를 사용해서 블로그 검색 결과를 조회할 것이다. 오픈 API 를 조회하는 기능의 인터페이스를 선언해보자. 파라미터로 검색 쿼리 문자열을 받고, 리턴 타입은 Mono<Blog> 이다.

해당, 인터페이스를 구현하는 구현체를 작성한다. 레디스 캐싱 서버에 저장되어 있는 데이터를 조회하는 로직이다. 참고로, Spring Data Reactive Redis 의 ReactiveRedisOperations 를 사용하였다.

블로그 정보를 조회하는 해당 로직은, BlogUseCase 를 구현하는 BlogService 에서 호출해서 사용한다. BlogController 에서는 BlogUseCase 를 호출하는데, BlogUseCase 의 구현체인 BlogService 대해서 알아야할 필요가 없는데, BlogService 에서 호출하는 기능 역시 알 필요가 없다.


BlogService 에서 호출하는 FindBlogByQueryPort 구현체인 SimpleRedisProvider에 대해서, 컨트롤러 입장에서는 전혀 알 필요가 없다.


쉽게 이해가 잘 안될것이다. 아래 클래스 다이어그램을 보면서 우리 같이 생각해보자.

FindBlogByQueryPort 를 구현하는 SimpleRedisProvider 클래스 구현체는, 네이밍에서 알수 있듯이 레디스를 사용한다. BlogController 입장에서는, 데이터가 어디에 저장 되어있는지 전혀 알 필요가 없다. 해당 구현체가 레디스 인지, RDBMS 인지, Elasticsearch 인지 전혀 알 필요가 없다는 사실이다. BlogService 에서의 코드를 확인해보자. FindBlogByQueryPort 타입의 simpleRedisProvider 빈을 주입받는다.

주입받은 simpleRedisProvider 빈을 사용해서, findBlogByQuery 메서드를 호출할 뿐이다. 레디스인지, RDBMS 인지 전혀 알 필요가 없다. 단지, Mono<Blog> 타입으로 리턴 받는다는 사실만 알 뿐이다.




UseCase --> UseCase (2)


지금까지는 블로그 정보를 조회하는 로직에 대해서 알아봤다. 자... 하지만, 블로그 정보를 조회하기 위해서는, 기존에 이미 블로그 데이터가 레디스 서버에 저장이 되어있어야 한다. 블로그 검색 결과를 업데이트 하는 로직을 살펴보자. 블로그 정보는 오픈 API 에서 가져오기로 하였다. 필자는 네이버 오픈 API 를 사용할 것인데, FindBlogByQueryPort 를 구현하는 NaverBlogProvider 라는 구현체를 아래와 같이 만들었다.

오픈 API 정보를 조회하면, 그 데이터를 레디스에 저장해야할 것이다. 레디스에 저장하는 UpdateBlogByQueryPort 라는 인터페이스를 하나 정의해보자.

BlogService 의 코드를 보면 아래와 같이

오픈 API 의 데이터를 먼저 조회하고, 그 다음에 Redis 에 저장하는 로직이다.  

UpdateBlogByQueryPort 를 구현하는 구현체는, 레디스 조회를 위해 만들었던 SimpleRedisProvider 컴포넌트에서 수행한다.

블로그 정보에 대한 업데이트에 대한 초기 요청은, 메시지 리스너에서 수행하는데, MessageListener 에서는, 데이터를 어디서 가져오는지, 어떤 캐시 저장소에 저장하는지 전혀 알지 못한다. 단지, MessageListener 클래스에서 BlogUseCase 의 updateBlogByQuery 메서드를 호출할 뿐이다.


참고로, UpdateBlogByQueryPort 와 FindBlogByQueryPort 는, 서로 각자의 책임을 갖고 있다. 즉, SRP (단일책임원칙)에 의해서 분리 되어있다. 하지만, 해당 두 인터페이스를 구현하는 SimpleRedisProvider 클래스는 두 책임을 모두 구현하는 형태로 구현을 하였다. 비록, 두개의 책임을 수행하는 클래스이지만, 다른 객체에서 이 클래스에 강하게 의존하지는 않기 때문에 크게 문제가 되지는 않는다고 생각한다.



저장소가 무엇인지 알 필요가 없다...라는 사실은

어떤 의미를 갖는가?


사실, 이 글에서 샘플 코드는 중요하지 않다. 우리가 알아야 하는 것은, 바로 디펜던시 의존성이다. 디펜던시는 동심원의 안쪽으로 의존한다. 바깥에 있는 영역은 안쪽에 의존하지만, 안쪽은 바깥쪽에 의존하지 않는다. 이 말을 풀어서 다시 설명하면, UpdateBlogByQueryPort 는 안쪽 원에서 UseCase 영역에 위치한다. SimpleRedisProvider 는 바깥쪽 영역 Provider 영역에 위치한다. SimpleRedisProvider 는 레디스 라는 저장소에 강한 의존성을 갖는다.

UpdateBlogByQueryPort 는 바깥쪽에 위치한 SimpleRedisProvider 에 의해서 영향을 받으면 안된다. 즉, UpdateBlogByQueryPort 는 어떤 저장소를 사용하는지에 대해서 알 필요가 없다. 저장소가 바뀐다고 해서 UpdateBlogByQueryPort 가 변해서는 안된다. BlogUseCase 를 구현하는 BlogService 에서 호출하는 UpdateBlogByQueryPort 는, 어떤 DB 를 사용하는지 모르기 때문에, 나중에 DB 를 변경하거나, 확장해도 전혀 문제가 되지 않는다. 즉, 아키텍처를 설계할 때 DB에 대한 선택을 최후로 미룰 수 있다는 얘기다. 우리는, 비즈니스 요구사항이 명확하게 정해진 이후에, DB 를 선택할 수 있고, 만약 어떤 문제가 발생해서 선택한 DB 를 사용하지 못하는 경우가 발생하면, 내부 로직에는 변함없이 DB 에 의존성을 갖고 있는 구현체만 변경하면 될 것이다.


이해를 돕기 위해서, 다른 예를 설명하겠다.


필자의 샘플에서는, 네이버 오픈API 를 사용한다. 만약 네이버 오픈API 카카오 오픈API 로 변경하고 싶다면 어떻게 될까?

동심원에 있는 FindBlogByQueryPort 는 변경이 되어서는 안된다. 바깥의 원에서 어떤일이 발생해도, 안쪽 원에 영향을 끼쳐서는 안된다. NaverBlogProvider 와 KakaoBlogProvider 는 FindBlogByQueryPort 를 구현하는 구현체이지만, 각자의 호출 로직은 다를 수 것이다. 오픈API 주소도 다를 것이고, 파라미터 또한 다를 것이다. 또한, NaverBlogProvider 에서는 WebClient 라는 모듈을 사용하는데, FindBlogByQueryPort 에서는 관여할 필요가 없다. 어떻게 호출하던 상관없이 Mono<Blog> 타입으로 전달만 잘 받으면 된다.



더 많은 고민이 필요하다.(1)


클린 아키텍처는 항상 정답은 아니다. 글 초반에 작성했듯이, 필자가 이 글을 쓰게 된 결정적인 이유는 필자가 경험했던 기술에 클린 아키텍처를 접목할 수 있는지에 대한 고민 때문이다. 우리는 JPA 를 많이 사용하는데, JPA 는 엔티티 매핑을 위해서 @Entity 어노테이션을 사용한다. 필자가 이 글에서 설명한 클린 아키텍처 이론에 의하면, Entity 객체는 DB 에 의존성을 가지면 안된다. 디펜던시 룰에 의하면, Entity 는 DB 를 어떤걸 사용하는지 모른다. JPA 뿐만 아니라, 스프링에서 제공하는 Spring Data 기술 역시 Entity 클래스에 의존성을 가질 수 밖에 없다. Spring Data 기술이 추상화 기술로 DB 변경에 대한 가능성이 열려있지만, 그럼에도 불구하고 Entity 객체의 의존성을 가질 수 밖에 없다. JPA, 스프링 등의 기술은 클린 아키텍처 를 보장해주지 않는다. 이 글은, 클린 아키텍처에 대해서 논하는 글이지만, 클린 아키텍처를 무조건 사용하기 위해서 JPA 를 사용하면 안된다는 내용은 아니다. 아키텍처 이론에는 정답이 없다. 상황에 맞게, 서비스에 맞게, 개발자들의 역량에 맞게 아키텍처 를 설계해야 할 것이다.



더 많은 고민이 필요하다.(2)


클린 아키텍처 이론을 적용하기 위해서, 필자는 인터페이스를 적극적으로 활용하였다. 하지만, 필자는 너무 복잡한 인터페이스 추상화로 인해서 고통받은 경험이 너무 많다. 불필요한 추상화는 오히려 애플리케이션의 복잡도만 증가시킬 것이다. 필자의 생각과 일치하는, "켄트 벡의 구현패턴"을 참고하길 바란다.


"인터페이스 추가에는 비용이 발생한다. 인터페이스를 사용하게 된 경제적 이유로는 소프트웨어는 예측하기 어렵다는 점을 들 수 있다. 프로그램에 유연성이 필요한 이유는 고객의 요구사항이 종잡을 수 없게 변화하고, 기술 변화도 예측할 수 없기 때문이다. 소프트웨어는 유연해야 하지만, 유연성에는 비용이 들고, 어떤 부분에서 유연성이 필요할지 에측하기란 쉽지 않다. 따라서 실제 필요해지는 경우에만 시스템에 유연성을 부여하자는 결론에 이른다.  - 켄트 벡"



마무리


주저리주저리 글을 작성했는데, 글이 너무 길어졌다. 재미없는 글이 되었는데, 혹시라도 정독해서 읽은 개발자가 있다면... 진심으로 감사하게 생각한다. 필자의 글은, 정답을 얘기하는 글이 아니다. 이 글은, 좋은 소프트웨어를 만들고 싶은 평범한 개발자의 고민이 담겨있는 글이다. 비록, "클린 아키텍처" 를 실무에서 적용할 수 있을지에 대해서는 아직 확신은 없지만, 소프트웨어를 잘 만들기 위한 노력은 끊임없이 해야할 것이다. 다음 글에서는 백엔드 애플리케이션의 캐싱 전략에 대해서 작성할 예정이다.


https://github.com/sieunkr/clean-architecture-sample


매거진의 이전글 Spring Boot DevTools 클래스로더 이슈
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari