IoT 시스템을 플랫폼으로 전환한 설계 회고
제주파크 전용으로 설계된 IoT 시스템은 한 파크를 안정적으로 운영하는 데에는 충분했다. 문제는 사업 확장이 시작된 뒤였다. 새 파크가 늘어날수록 코드 복제, 조건 분기, 운영 규칙 차이가 함께 늘어났고, "이번에는 빨리 붙이자"는 선택이 다음 확장의 비용으로 돌아오기 시작했다.
당시 핵심 문제는 기능 부족이 아니었다. 기존 구조가 한 파크를 빠르게 운영하는 데 최적화되어 있었고, 여러 파크를 공통 플랫폼으로 묶는 데 필요한 책임 분리와 변경 경계가 없었다.
이 글에서는 그 상황을 단순한 리팩터링 과제가 아니라 "멀티파크 확장을 감당할 수 있는 구조를 다시 정의하는 일"로 어떻게 바라봤는지, 어떤 대안을 검토했고 왜 도메인 중심 구조와 Hexagonal Architecture를 선택했는지, 그리고 실제 전환 과정에서 무엇이 가장 어려웠는지 정리한다.
1. 왜 이 문제를 해결해야 했는가
기존 시스템은 제주파크 운영 요구에 맞춰 빠르게 성장한 구조였다. 그 덕분에 초기에는 개발 속도와 운영 대응 속도가 모두 괜찮았다. 하지만 파크가 하나 더 늘어나는 순간부터 비용 구조가 달라졌다.
- 새로운 파크를 붙일 때마다 기존 로직을 복제하거나 조건문을 추가해야 했다
- 파크별 운영 규칙이 비즈니스 로직이 아니라 곳곳의 분기문으로 흩어졌다
- 특정 기능을 수정할 때 다른 파크까지 영향이 갈 가능성을 항상 같이 검토해야 했다
- 외부 장비나 API 차이가 도메인 규칙과 같은 레이어에 섞여 들어왔다
이 상태는 "지금은 동작한다"는 의미에서는 안전했지만, "다음 확장을 싸게 만들 수 있는가"라는 질문에는 그렇지 않았다.
그래서 문제를 기능 개발 속도가 아니라 확장할수록 비용이 커지는 구조적 부채로 보기 시작했다. 핵심 질문은 하나였다.
새로운 파크가 추가돼도 기존 도메인을 거의 건드리지 않고 확장할 수 있는가?
2. 시스템 요구사항과 제약을 다시 정리했다
구조를 바꾸기 전에 먼저 이번 전환이 만족해야 할 조건을 분리해서 봤다. 이 단계를 거치지 않으면 아키텍처 논의가 취향 싸움으로 흐르기 쉬웠다.
| 요구사항 | 왜 필요했는가 | 당시 제약 |
|---|---|---|
| 파크별 정책 분리 | 파크가 늘어날수록 조건문 복제를 줄여야 했다 | 이미 운영 중인 로직을 한 번에 끊을 수 없었다 |
| 외부 연동 격리 | 장비, API, 저장소 차이가 도메인 규칙을 오염시키고 있었다 | 외부 인터페이스 스펙이 완전히 안정적이지 않았다 |
| 점진 전환 가능성 | 전체 시스템을 한 번에 갈아엎을 수 없었다 | 운영 중단 없이 부분 전환해야 했다 |
| 테스트 가능성 | 변경이 곧 리스크가 되는 구조를 끊어야 했다 | 기존 코드베이스에는 테스트 기반이 거의 없었다 |
이렇게 정리하고 나니 문제 정의도 더 선명해졌다. 이번 전환의 목표는 "코드를 더 예쁘게 정리한다"가 아니라 파크 추가 비용과 변경 리스크를 구조적으로 낮추는 것이었다.
3. 선택 가능한 대안들은 무엇이었는가
문제를 다시 정의한 뒤에는 세 가지 방향을 비교했다. 중요했던 건 "무엇이 더 멋져 보이는가"가 아니라 현재 제약에서 어떤 선택이 가장 오래 버티는가였다.
1) 기존 구조 유지 + 분기 최소화
- 단기적으로 가장 빠르고 안전해 보였다
- 현재 운영 흐름을 거의 건드리지 않아도 됐다
- 팀이 새 개념을 크게 학습하지 않아도 됐다
하지만 이 방식은 문제를 해결하지 않고 뒤로 미루는 선택에 가까웠다. 파크가 늘수록 조건문과 예외 처리가 다시 쌓일 수밖에 없었기 때문이다.
2) 프레임워크 중심 구조 개편
- 패키지 구조나 레이어 규칙을 표준화하기 좋았다
- 코드를 일정한 형태로 정리하는 효과는 기대할 수 있었다
반면 이 접근은 무엇이 도메인 규칙이고 무엇이 구현 세부사항인지를 충분히 드러내지 못했다. 겉보기 구조는 바뀌어도, 책임 경계가 그대로면 확장 비용은 다시 누적된다.
3) 도메인 중심 구조 재설계
- 도메인 경계를 먼저 정의하고 책임을 다시 배치할 수 있었다
- 외부 연동을 포트와 어댑터로 분리해 변경 영향을 제한할 수 있었다
- 테스트 가능한 핵심 규칙을 별도 레이어로 끌어올릴 수 있었다
가장 시간이 걸리는 선택이었지만, 멀티파크 확장을 전제로 보면 가장 정직한 선택이기도 했다.
결론적으로 단기 생산성보다 장기 확장 비용을 줄이는 쪽이 더 중요하다고 판단했다.
4. 그래서 어떤 선택을 했는가
최종적으로는 도메인 중심 구조를 다시 세우고, 도메인 바깥의 의존성을 Hexagonal Architecture로 감싸는 방식을 선택했다.
핵심은 기술 용어 자체가 아니라 경계를 어디에 두느냐였다.
- 메타데이터, 상태, 기록, 로그, 제어 같은 핵심 개념을 도메인 기준으로 다시 나눴다
- 비즈니스 규칙은 도메인 레이어에 두고, 외부 시스템 연동은 포트와 어댑터로 밀어냈다
- "어떤 장비를 쓰는가"보다 "도메인이 어떤 입력과 출력을 기대하는가"를 먼저 보게 만들었다
구조를 간단히 표현하면 아래와 비슷했다.
Inbound Adapter
-> Application Service
-> Domain Model / Domain Service
-> Port
-> Outbound Adapter이렇게 두면 파크별 차이와 외부 시스템 차이는 어댑터 레이어에서 흡수하고, 핵심 비즈니스 규칙은 도메인에서 유지할 수 있다.
이 선택의 목적은 단순했다.
비즈니스 규칙이 바뀌더라도 시스템 전체가 같이 흔들리지 않게 만들자.
5. 구현에서 부딪힌 문제와 어떻게 수정했는가
이 전환에서 어려웠던 지점은 아키텍처 패턴 자체보다 "기존 시스템의 무엇을 먼저 고정해야 하는가"를 판단하는 일이었다.
1) 구조를 바꾸기 전에 먼저 구조를 보이게 만들었다
처음에는 곧바로 패키지 분리와 레이어 이동부터 하고 싶었다. 하지만 그렇게 접근하면 팀마다 현재 문제를 다르게 이해하고 있다는 사실이 바로 드러났다.
그래서 코드 변경보다 먼저 아래 자료를 만들었다.
- 기존 ERD
- 주요 Inbound / Outbound 흐름
- 파크별 규칙이 섞여 있는 지점
- 외부 연동 의존성이 도메인 로직에 침투한 지점
이 작업을 하고 나서야 "어디서 책임이 흐려지는가"를 팀이 같은 그림으로 보기 시작했다. 결국 설계 전환의 첫 단계는 코드 이동이 아니라 문제를 같은 언어로 설명할 수 있게 만드는 일이었다.
2) 한 번에 다 바꾸려는 시도가 가장 먼저 실패했다
처음에는 여러 도메인을 같이 정리하려고 했다. 그런데 범위가 커지자 논의가 쉽게 추상화로 올라갔고, "지금 무엇을 바꾸는지"보다 "이상적인 구조가 무엇인지"를 말하는 시간이 길어졌다.
그래서 방향을 바꿨다.
- 티켓 도메인처럼 영향 범위가 비교적 선명한 영역부터 시작했다
- 작은 성공 사례를 먼저 만든 뒤 다른 도메인으로 확장했다
- 구조의 필요성을 문서가 아니라 코드와 테스트로 설명하려고 했다
이 수정 덕분에 아키텍처 논의가 이론에서 실행으로 내려왔다.
3) 테스트는 마지막 검증이 아니라 전환 조건이었다
기존 코드베이스에는 테스트 코드가 거의 없었다. 처음에는 구조를 먼저 바꾸고 테스트를 나중에 붙여도 된다고 생각했다. 하지만 그렇게 하면 전환 자체가 너무 불안정해졌다.
그래서 접근 순서를 다시 잡았다.
- 도메인 계층에서 먼저 테스트 가능한 단위를 만들었다
- 외부 의존성은 Mock이나 Adapter로 분리했다
- 핵심 규칙은 도메인 테스트로 고정하고, 연동 차이는 어댑터 테스트로 분리했다
이때부터 테스트는 품질 장치라기보다 팀이 안심하고 구조를 옮길 수 있게 만드는 안전장치가 됐다.
6. 어떻게 검증했고 무엇이 달라졌는가
정량 수치를 충분히 남기지 못한 점은 분명한 한계였다. 다만 전환 이후에는 운영과 변경 과정에서 몇 가지 변화가 분명하게 보였다.
| 확인한 변화 | 전환 전 | 전환 후 |
|---|---|---|
| 파크 추가 논의 방식 | 코드 복제와 예외 분기부터 떠올렸다 | 설정, 도메인 확장 포인트, 어댑터 차이부터 본다 |
| 변경 영향 범위 파악 | 여러 파크와 외부 연동을 함께 훑어야 했다 | 도메인 경계와 포트 기준으로 영향 범위를 좁혀 볼 수 있다 |
| 테스트 기반 수정 | 변경 자체가 부담이 컸다 | 핵심 규칙은 도메인 테스트로 먼저 검증할 수 있다 |
무엇보다 체감이 컸던 변화는 심리적 비용이었다. 예전에는 "이 코드를 고치면 다른 파크가 깨지지 않을까?"가 먼저 떠올랐다. 전환 이후에는 적어도 어디까지를 먼저 확인해야 하는지가 훨씬 분명해졌다.
반대로 남은 한계도 있었다.
- 파크 추가 리드타임 같은 정량 지표를 초기에 수집하지 못했다
- 일부 레거시 경로는 여전히 완전히 분리되지 않았다
- 팀 합의 문서가 코드 변경 속도를 따라오지 못하는 구간이 있었다
다음에는 구조 전환과 동시에 리드타임, 변경 실패율, 테스트 커버 범위 같은 지표를 함께 남길 생각이다.
7. 이 경험 이후로 남은 판단 기준
이번 일을 지나며 몇 가지 기준이 더 선명해졌다.
- 확장을 전제로 하지 않은 구조는 기능이 늘수록 반드시 비용으로 돌아온다
- 아키텍처의 핵심은 프레임워크 선택이 아니라 책임과 변경 경계를 어떻게 자르느냐에 있다
- 테스트 가능한 구조가 있어야 팀이 구조를 바꾸는 결정을 실제로 할 수 있다
- 점진 전환이 필요한 시스템일수록 "완벽한 최종 구조"보다 "작게 옮길 수 있는 경계"가 더 중요하다
결국 좋은 구조는 코드가 예뻐 보이는 상태가 아니라, 새 요구사항이 들어왔을 때 어느 부분을 바꾸고 어느 부분을 지켜야 하는지가 드러나는 상태라고 본다.
8. 다음에 다시 한다면
다시 같은 일을 한다면 코드보다 먼저 아래 두 가지를 더 빠르게 고정할 것이다.
- 도메인 모델과 책임 경계를 팀 단위로 먼저 짧게 합의한다
- 전환 효과를 보여줄 지표를 초기부터 같이 수집한다
이번 경험을 지나며 더 분명해진 것은, 아키텍처 전환이 기술 과제이면서 동시에 설득 과제라는 점이다. 좋은 구조는 혼자 설계해서 끝나는 것이 아니라, 팀이 같은 기준으로 변경을 이어갈 수 있을 때 비로소 유지된다.