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

2019-03-02·programming

김개발 이야기

김개발은 커머스를 만들고 있다. 데이터베이스를 기준으로 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를 처음 학습할 때 HATEOAS(Hypermedia as the engine of application state)를 제대로 이해하지 못했다. POST 요청은 생성의 의미이며, '/users'처럼 url path에 복수형으로 리스소를 쓴다 정도를 이해했을 뿐이었다. 꽤나 오랜 기간 내가 REST API를 만든다고 착각했다. 놀랍게도 많은 사람들이 REST가 아닌 REST API를 만들고 있다.

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

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

은총알은 없다

유명한 말이라 마치 내 생각처럼 적는 게 쑥스럽지만 은총 알은 없다. 정답이 되는 아키텍처란 나는 아직 보지 못했다. 그저 상황에 맞는 최선의 선택이 있을 뿐이다. 그렇다면 정답도 없는 '좋은' 아키텍처란 왜 필요할까. 일관적인 구조화된 코드 패턴들은 소프트웨어 결함을 줄이고 개발자의 생산성을 올려주기 때문에, 유저에게 가치 전달한다는 목표를 달성하는데 중요하다. 다만 아키텍처 자체가 목표가 돼서는 안 된다. 유저에게 가치를 전달하기 위해 아키텍처에 투자하는 것과 '멋진 아키텍처를 구성하는 것'은 구분해야 한다.

개발자는 좋은 아키텍처에 대해 신중해야 한다. 당장 할 수 있는 선택과 머지 않아 해야 하는 것 그리고 포기할 것을 냉정하게 생각해야 한다.

커뮤니케이션 그리고 지속적인 노력

그럼 어떻게 '좋은' 아키텍처를 가져올 수 있을까. 구현과 상관 없이 그 방식이 논리적인 모순이 없는지, 유저 관점에서 자연스러운지 살펴봐야 한다. 그러지 않으면 그 모순과 부자연스러움이 아키텍처에도 드러난다. 그러나 현실은 매우 복잡하기에 완벽히 해결할 수 있는 문제는 아니다.

한편 여러 작업자들 간 얼마나 같은 생각을 하고 있는지가 중요하다. 디자이너, 개발자, 기획자 등 여러 사람들이 같은 곳을 바라보는 것은 쉽지 않다. 차근차근 가장 병목이 되는 미스커뮤니케이션 요소를 발굴하고 그를 위한 적절한 조치를 해야 한다. 제품과 조직이 복잡해짐에 따라 구성원들은 커뮤니케이션에 대한 부담감과 피로감은 날로 늘어간다. 구성원들이 희망을 잃어버리고 보수화 된다. 그저 workaround 만을 고민하는 maker 조직은 미래가 없다. 그리고 그것은 사업적으로도 큰 위기로 이어진다고 생각한다.

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

김개발 이야기로 돌아가 보자. 어떤 것이 문제일까? 너무 추상적인 서비스 네이밍이 문제일 수도 있겠다. 극단적으로는 UserService 에는 모든 메서드가 작성될 자격을 얻을 수도 있다. 유저와 관련이 없는 기능이 얼마나 있을까.

김개발과 같은 고민을 하면서 나는 "서비스란 무엇인가?"라는 물음을 던지게 되었다. 우선 스프링의 @Service 어노테이션의 구현 파일을 봤다. 작성만 하던 Service가 무엇인지 확실히 하고 넘어가고 싶었다.

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

그 뒤로 DDD(Domain-Driven Design)에 더 관심이 생겼다. 같이 개발하는 회사 동료들과 함께 가볍게 책을 읽었다. 우리는 같은 곳을 바라보기 시작했다. 일관되고 구조화된 코드를 목표로 하며 생산적인 토론이 이루어졌다. 우선 DDD라는 기준이 생기니 당장 할 수 있는 것과, 미래야 달성해야 하는 것을 나누고 조금씩 코드 품질을 올려갔다.

끝맺음

사실 DDD Start!를 회사 동료들과 읽고 내용을 정리하는 글을 쓰려했었다. 도중에 코드와 내용을 옮겨 적는 것보다는, 내 이야기를 적어보기로 결심했다. 좋은 자료는 책이나 인터넷에 많으니까 말이다.

혹시 김 개발과 같은 고민을 하는 개발자라면, spring을 하지 않아도 spring과 DDD 학습해보길 추천한다. DDD와 Spring이라는 거인에 어깨에 올라와 개발을 해보니 흥미로운 것들이 많다. 아마 시작한 지 얼마 되지 않아서일 수도 있다. 초심자는 늘 행복하니까.

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

참고

Buy Me A Coffee