모든 글

코어 책임과 대리키로 변경에 대비하기

공유 등록 규칙을 코어에 모으고 필요할 때만 격리를 강화하며, 바뀔 수 있는 비즈니스 유일성은 기본키와 분리하는 설계를 다룬다.

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

단말기 등록 규칙이 API, 어드민 애플리케이션, 배치에서 모두 필요합니다. DAO와 등록 로직은 각 모듈에 흩어져 있어서 중복 검사 규칙을 바꾸려면 모든 호출부가 그 내용을 알아야 합니다. 그렇다고 전부 코어로 옮기자니 다른 걱정이 생깁니다. 코어 클래스가 알 필요 없는 애플리케이션까지 퍼지는 것은 아닐까?

여기에는 서로 다른 두 가지 설계 결정이 있습니다. 하나는 등록 행위의 주인을 어디에 둘지이고, 다른 하나는 비즈니스에서 유일성의 정의가 바뀔 때 데이터베이스의 식별자를 어떻게 표현할지입니다. 둘 다 다음 변경 비용을 낮춰야 하지만, 처음부터 가장 복잡한 구조를 택할 필요는 없습니다.

구체적인 등록 규칙의 주인을 코어에 둡니다

당장의 문제는 소유권이 파편화되어 있다는 것입니다. 단말기 등록은 하나의 기능인데 각각의 실행 애플리케이션이 처리 과정의 일부를 알고 있습니다. DeviceCoreService처럼 넓은 이름은 경계를 분명하게 만들어 주지 못합니다. 레이어는 높고, 무슨 일을 하는지와 어떤 규칙을 소유하는지도 이름에서 드러나지 않습니다.

DeviceAppender처럼 구체적인 컴포넌트는 책임을 보이게 합니다. 코어의 구현 레이어에 두고 등록과 중복 검사 로직을 한곳으로 모읍니다. API와 어드민 애플리케이션, 배치는 같은 구현체를 호출하고 비즈니스 규칙은 한곳에서 바뀝니다.

이 결정은 JPA 사용 여부와 관계가 없습니다. 영속성 기술을 바꾸거나 JPA용 추상화를 더한다고 해서 단말기 등록의 주인이 정해지지는 않습니다. 먼저 개념을 담당하는 믿을 만한 구현체를 만들고, 영속성은 그 아래에서 사용하는 재료 중 하나로 두면 됩니다.

구현 레이어라는 위치도 중요합니다. 이 컴포넌트는 단말기를 등록하는 구체적인 도구이지, 모든 호출부가 전체 흐름을 알게 만드는 또 하나의 상위 서비스가 아닙니다. 명확한 이름과 한 단계 낮은 레이어가 사용 범위를 좁혀 줍니다.

팀이 운영할 수 있는 경계에서 시작합니다

여러 모듈에서 코어 컴포넌트를 볼 수 있으면 구조가 지저분해 보일 수 있습니다. 그래도 팀이 사용 방식을 통제할 수 있다면 충분히 합리적인 첫 설계입니다. 짧은 내부 규칙과 일상적인 코드 리뷰, 팀의 소통으로 호출부가 구현체를 우회하지 않게 할 수 있습니다. 새 동료가 단말기 등록 작업을 맡아도 기존 구현체가 있는지 묻고, 코어에서 찾고, 리뷰를 통해 규칙을 익힐 수 있습니다.

이 선택은 변경 범위를 작게 유지합니다. 별도의 실행 경계를 더하지 않고 행위를 한곳에 모읍니다. 리뷰와 가이드만으로 규칙을 지킬 수 있다면 현재 가진 수단으로 현재 문제를 해결한 셈입니다.

클래스가 노출되는 것 자체가 실제 비용이 될 때는 더 강한 선택지가 있습니다. API, 어드민 애플리케이션, 배치가 이미 각각 별도 서버로 실행된다면 코어 API만 코어 모듈의 진입점으로 둘 수 있습니다. 각 애플리케이션은 코어 구현 클래스를 의존하는 대신 이 API를 호출합니다. 등록 개념은 실행 시점의 계약 뒤로 격리되고 코어 코드도 모든 애플리케이션으로 퍼지지 않습니다.

이 격리는 장기적인 소유권과 인수인계에 도움이 될 수 있습니다. 외부를 상대하는 API가 이미 너무 많은 책임을 가진 상황에서도 고려할 만합니다. 하지만 오버엔지니어링이 될 수도 있습니다. 클래스 의존성이 마음에 들지 않는다는 이유만으로 별도 배포와 네트워크 호출, 추가 계약의 비용을 치르기에는 큽니다. 먼저 코어 컴포넌트를 직접 사용하는 구조로 운영하고, 실행 경계가 충분한 가치를 줄 때 코어 API로 넘어가는 편이 낫습니다.

모듈을 더 잘게 나누는 방법도 떠올릴 수 있지만, 개념을 이해하기 전에는 특히 잘못되기 쉽습니다. device 하나만을 위한 모듈은 관련 책임을 가로지르며 의존성과 코드, 유지보수 문제를 새로 만들 수 있습니다. 나중에 단말기와 부수 행위를 아우르는 안정된 개념이 드러난다면 응집된 모듈을 만들 근거가 생길 수 있습니다. 성급하게 쪼갠다고 그 이해가 생기지는 않습니다.

비즈니스 유일성을 기본키에서 분리합니다

중복 규칙은 다른 종류의 결합도 보여 줍니다. 원래 단말기 번호 하나가 유일했다고 해보겠습니다. 요구사항이 바뀌어 브랜드가 다르면 같은 번호를 허용합니다. 단말기 번호 자체가 테이블의 기본키라면 비즈니스 정책 하나를 바꾸는 일이 기본키 변경으로 이어집니다.

별도의 대리키 id를 기본키로 두는 편이 좋습니다. 자동 증가 값, UUID, ULID 등 어떤 생성 방식을 쓸지는 별개의 결정입니다. 핵심은 바뀔 수 있는 비즈니스 데이터로 행의 식별자를 정의하지 않는 것입니다.

현재의 비즈니스 규칙은 유니크 제약이나 유니크 인덱스로 표현합니다. 처음에는 단말기 번호에 유일성을 걸 수 있습니다. 단말기 번호와 브랜드의 조합이 유일해야 하는 것으로 규칙이 바뀌면 기존 제약을 두 컬럼의 복합 제약으로 바꿉니다. 행의 기본 식별자는 유지되고 그 주변의 비즈니스 규칙만 변합니다.

그렇다고 운영 중인 기존 테이블의 마이그레이션이 사라지는 것은 아닙니다. 이미 비즈니스 데이터를 기본키로 쓰고 있다면 변경 과정에서 데이터 마이그레이션이 필요할 수 있고, 안전한 방법은 데이터 양과 현재 스키마에 따라 달라집니다. 행이 몇 개뿐인 테이블과 큰 테이블은 전혀 다르게 다룰 수 있습니다. 이 맥락을 모르는 상태에서 운영 중인 기본키를 가볍게 바꾸는 선택은 선호하기 어렵습니다.

먼저 구체적인 코어 구현체 아래에 규칙을 모읍니다. 팀이 그 경계를 관리할 수 있는 동안은 리뷰로 지키고, 실제 격리 가치가 비용보다 클 때만 코어 API를 도입합니다. 대리키를 두면 다음 유일성 변경도 식별자 재작성 대신 제약 변경으로 끝낼 수 있습니다.