그런 아키텍쳐로 괜찮은가?

비지니스 로직은 서비스 레이어지! UserService! OrderService!

김개발 이야기

김개발은 커머스를 만들고 있다. 데이터베이스를 기준으로 User, Order, Product 객체를 정의한다. 상태를 변경하거나 읽는 것을 "비즈니스 로직"이라 부르고 이를 "서비스"에 구현한다.

"비즈니스 로직은 서비스 레이어지! UserService! OrderService!"

UserService class를 정의하고 데이터베이스에 유저를 조회/수정/삭제/생성하는 메서드를 만들었다. 개발에 자신 있던 김개발은 CRUD쯤(?)은 가볍게 프레임워크나 라이브러리를 이용하여 작성한다. 비슷하게 주문을 위한 OrderService 도 만들었다. 주문을 할 때에는 유저 정보를 조회할 필요가 있어 OrderService에서 UserService를 참조한다.

1

어느 날 김 개발은 유저의 주문내역을 보여주는 기능을 만들어야 했다. UserService에서 OrderService를 참조하여 기능을 만들려는 순간, 김 개발은 순환 참조 에러를 마주한다.

2

순환 참조를 한두 번 겪어본 것이 아니다. 김개발은 침착하게 OrderHistoryService를 만든다. OrderHistoryService에서는 UserService와 OrderService를 참조하여 '유저의 주문내역'기능을 일정 안에 구현했다.

3

1년이 지나고 같은 로직을 조금 다르게 구현한 서비스들이 마구 생겨났다. 많은 부분을 담당하는 서비스를 순환 참조 때문에 재사용하지 못했다. 수정에는 늘 버그가 따라다녔다. 테스트 코드는 코드를 조금만 수정하면 변경 비용이 매우 컸다. 김개발은 무언가 잘못되어 간다는 것을 느꼈다.

dh

출처

같은 REST API를 생각하지 않는다.

이응준 님의 그런 REST API로 괜찮은가 발표를 보고 충격과 감동을 받았다. 의도된 의미를 정확히 이해하는 것은 보기보다 매우 많이 어려울 수 있겠다는 생각이 들었다. 예를 들면 나는 REST를 처음 학습할 때 HATEOAS(Hypermedia as the engine of application state)를 제대로 이해하지 못했다. POST 요청은 생성의 의미이며, '/users'처럼 url path에 복수형으로 리스소를 쓴다 정도를 이해했을 뿐이었다. 꽤나 오랜 기간 내가 REST API를 만든다고 착각했다. 놀랍게도 많은 사람들이 REST가 아닌 REST API를 만들고 있다.

소프트웨어 공학에서 정립된 개념을 이해하려는 노력은 중요하다. '완벽하게' 이해하지 못하는 것에 대해 지적할 의도는 없다. 더 중요한 것은 고객에게 가치를 전달하는 일이기 때문이다. 글을 쓸 때 연필이 맞는지 볼펜이 맞는지는 상황에 따라 다르다. 그리고 지금 빨리 글을 써야 한다면 글을 써야 함을 잊어선 안된다.

REST API 처럼, 아키텍처와 관련하여 '도메인'과 '비즈니스 로직' 두 개념을 개발자마다 다르게 이해한다. 사람들끼리 생각이 벌어지면 벌어질수록 소프트웨어 개발비용은 불어난다. 따라서 개발자들은 같은 방향을 바라볼 필요가 있다. 소프트웨어에서 코드가 아름다운 일관성을 가지면 작업자는 비교적 쉽게 작업을 할 수 있다. 그러나 이런 엄격한 아름다움을 갖는 경우는 매우 드물다. 당신이 만지고 있는 코드는 대부분 아키텍처가 아름답지 못할 것이다.

은총알은 없고, 일관된 아름다움은 있다

정답이 되는 은총알 같은 아키텍처는 없지만, 한가지 추구해야할 가치는 일관성이다. 일관되어 예측가능하고, 구조화된 코드 패턴들은 소프트웨어 결함을 줄이고 개발자의 생산성을 올려준다. 따라서 유저에게 빠르게 가치 전달한다는 목표를 달성하는데 큰 역할을 한다.

어떻게 일간된 아름다움을 가져올 수 있을까. 여러 작업자들 간 얼마나 같은 생각을 하고 있는지가 중요하다. 단발적인 변화가 또다른 파편화를 만들지 않도록 신중하게 변화를 이끌어나가야 한다. 혹은 특정 영역에 변화를 한정하여 아키텍쳐 전체의 맥락을 잃어버리지 않도록 한다. 무수한 파편화 속에서 예측가능한 설계의 인지가 불가능한 상태로 접어든 프로젝트는 유지보수가 매우 힘들다.

김개발 이야기로 돌아가 보자. 어떤 것이 문제일까? 너무 추상적이고 넓은 의미를 포괄하는 네이밍이 문제일 수도 있겠다. UserService 에는 모든 메서드가 작성될 자격을 얻을 수도 있다. 유저와 관련이 없는 기능이 없지 않느냐라고 우겨볼 수도 있으리라. 엔티티 객체에 표현될 코드들이 Service 에 작성한 것일까. 비효율적이거나 잘못된 코드들도 일관성을 갖는다면 개선또한 일관되게 진행가능하다.

거인의 어깨 올라가서 생각해보기로 했다.

개발하다 문득 스프링의 서비스란 뭔지에 대해 고민하게 되었다. 더불어 사람들이 서비스를 어떻게 이해하는지가 궁금해졌다. 작성만 하던 Service가 무엇인지 확실히 하고 넘어가고 싶었다. @Service 어노테이션의 구현 파일을 봤다.

"Indicates that an annotated class is a "Service", originally defined by Domain-Driven Design (Evans, 2003)"

Domain-Driven Design...? 실체는 모르지만 단어는 익숙한 그것이다. 그 뒤로 DDD에 관심이 생겼다. 같이 개발하는 회사 동료들과 함께 가볍게 책자고 제안했다. 팀원들은 같은 곳을 바라보기 시작했다. 일관되고 구조화된 코드를 목표로 하며 생산적인 토론이 이루어졌다. 우선 DDD라는 기준이 생기니 당장 할 수 있는 것과, 미래야 달성해야 하는 것을 나누고 조금씩 코드 품질을 올려갔다. 일관됨을 위해 같이 논의할 장이 마련된 것이다.

DDD가 모든것을 해결해 주지는 않는다. 장점은 먼저 부각될 것이고, 단점은 어느 순간 나의 뒤통수를 칠 것이다. 마이클 타이슨이 이야기했다. "모두 다 그럴듯한 계획을 갖고 있다. 처맞기 전에는." 아키텍처도 그러할 것이다. 어떤 아키텍처가 훌륭하다는 말을 하고자 하는 게 아니다. 거인의 어깨에 올라가며 느끼는 즐거움을 이야기하고 싶을 뿐이다. 그 즐거움은 순수하게 알아가는 즐거움이자, 고객에게 조금 더 빠르게 좋은 제품을 전달할 수 있는 즐거움일 것이다.

DDD는 모르지만 Service 와 Reposiotry 를 열심히 구현했다. 내가 DDD에 대해 관심이 생기고 회사 사람들과 함께 공부하며, 논의하고 코드를 조금씩 개선해나가는 즐거운 경험이 조금이라도 전달하고 싶었다.

참고

Buy Me A Coffee