이전 포스팅에서 코드를 안전하고 빠르게 배포하기 위한 자동화된 파이프라인의 중요성에 대해 알아보았다.

https://hsunnystory.tistory.com/269

 

 

이제 스크럼을 통해 요구사항을 관리하고, CI/CD를 통해 하루에도 몇 번씩 배포할 수 있는 인프라 체력까지 갖추었다고 가정해 보자. 하지만 정작 백엔드 시스템의 핵심인 아키텍처 자체가 거대한 하나의 덩어리(Monolithic)로 강하게 결합되어 있다면 어떨까? 작은 버튼 하나를 추가하기 위해 수십 개의클래스를 수정해야 한다면, 아무리 훌륭한 파이프라인이 있어도 결코 애자일하게 움직일 수 없다.

 

애자일 시리지의 마지막인 이번 편에서는 요구사항의 잦은 변경을 두려워하지 않고 진화하는 아키텍처를 설계하는 방법에 대해 알아본다.

 

1. 처음부터 완벽한 설계는 없다 (No BDUF)

전통적인 워터폴(Waterfall) 방식에서는 개발을 시작하기 전, 완벽한 ERD와 시스템 아키텍처를 그리는 데 수개월을 쏟는다. 이를 BUDF(Big Design Up Front)라고 부른다.

 

하지만 애자일 환경에서 비즈니스 요구사항은 반드시 변한다. 초기에 설계한 완벽한 DB 스키마는 한 달 뒤 기획이 엎어지면 거대한 기술 부채로 전락한다.

 

따라서 애자일 시대의 아키텍처는 나중에 언제든 갈아끼울 수 있도록(Replaceability) 설계하는 것이 핵심이다. 우리는 어떤 결정을 최대한 미루기(Defer) 위해 노력해야 한다. DB를 MySQL로 할지 MongoDB로 할지, 결제 연동을 카카오페이로 할지 토스페이로 할지 초기에 완벽히 결정하지 않아도 비즈니스 로직을 개발할 수 있어야 한다.

 

2. [Bad vs Good Code] 진화하는 설계를 위한 DIP (의존성 역전 원칙)

비즈니스 로직을 외부 기술(DB, 인프라, 외부 API)로부터 보호하여 진화할수 있도록 만드는 가장 강력한 무기는 DIP(의존성 역전 원칙)다.

 

주문을 처리하고 결제 모듈을 호출하는 시스템을 생각해보자. 처음에는 빠른 출시를 위해 카카오페이 하나만 연동하기로 했다.

 

[Bad Code] 기술에 종속되어 확장이 불가능한 아키텍처

비즈니스의 핵심인 OrderService가 외부 인프라 기술인 KakaoPayClient라는 구체 클래스에 강하게 결합되어 있다. 추후 네이버페이, 토스페이가 추가되거나 결제사가 아예 변경된다면 핵심 비즈니스 로직을 통쨀로 뜯어고쳐야 한다.

 

@Service
@RequiredArgsConstructor
public class OrderService {
    
    // 외부 기술(구체 클래스)에 강하게 의존하고 있음
    private final KakaoPayClient kakaoPayClient; 

    public void processOrder(Order order) {
        // 1. 주문 로직 처리
        order.verify();
        
        // 2. 결제 호출 (요구사항 변경 시 이 핵심 로직이 오염됨)
        kakaoPayClient.pay(order.getTotalAmount());
    }
}

 

[Good Code] DIP를 통해 기술 결정을 미루는 헥사고널(포트 앤 어댑터) 아키텍처

애자일 아키텍처에서는 비즈니스 로직이 외부 기술을 직접 쳐다보지 않도록 인터페이스(Port)를 중간에 둔다. 그리고 외부 기술은 이 인터페이스를 구현하는 어댑터(Adapter) 형태로 제공된다.

 

// 1. 핵심 비즈니스 도메인 계층에 위치한 인터페이스 (Port)
// "나는 누가 결제하든 상관없어. 그냥 이 규격에 맞춰서 결제만 해줘."
public interface PaymentPort {
    void processPayment(long amount);
}

// 2. 외부 인프라 계층에 위치한 구현체 (Adapter)
@Component
public class KakaoPayAdapter implements PaymentPort {
    @Override
    public void processPayment(long amount) {
        // 실제 카카오페이 API 호출 로직
    }
}

// 3. 기술 변경에 영향받지 않는 견고한 비즈니스 로직
@Service
@RequiredArgsConstructor
public class OrderService {
    
    // 구체 기술이 아닌 도메인 인터페이스에만 의존 (DIP 준수)
    private final PaymentPort paymentPort;

    public void processOrder(Order order) {
        order.verify();
        
        // 결제사가 네이버페이로 바뀌든 토스페이로 바뀌든 OrderService는 단 한 줄도 수정되지 않는다.
        paymentPort.processPayment(order.getTotalAmount());
    }
}

 

이렇게 설계하면, 비즈니스 로직은 보호된 채로 바깥쪽의 기술 스택(DB, 결제사 등)만 언제든지 새롭게 교체하며 시스템을 진화시킬 수 있다.

 

3. 마이크로 서비스 아키텍처(MSA)와 콘웨이의 법칙

애자일 조직이 성장함에 따라 자연스럽게 도입을 고려하게 되는 것이 바로 MSA(Microservices Architecture)다.

 

수십 명의 개발자가 하나의 거대한 모놀리식(Monolithic) 시스템을 만지다 보면, 내가 수정한 코드 한 줄이 다른 팀의 기능에 장애를 일으킬까 두려워 배포를 망설이게 된다. 즉, 코드가 무거워지면 애자일의 생명인 빠른 피드백이 불가능해진다.

 

 - 콘웨이의 법칙(Conway's Law): 조직의 시스템 아키텍처는 그 조직의 의사소통 구조를 닮는다.

 - 비즈니스 도메인별(주문팀, 결제팀, 배송팀 등)로 작고 독립적인 목적 조직(Cross-functional Team)을 구성하는 것이 애자일의 기본이다.

 - 조직이 분리되었다면, 그들이 다루는 시스템과 배포 파이프라인 역시 독립적으로 분리되어야 한다. 이것이 MSA가 애자일 조직에 필연적으로 요구되는 이유다. 우리 팀이 담당하는 배송 버시브만 독립적으로 수정하고, 오늘 당장 배포하겠다가 가능해져야 진정한 애자일 팀이라 할 수 있다.

 

4. 결론

총 9편의 시리즈를 통해 애자일의 선언문부터 스크럼, 칸반, TDD, CI/CD, 그리고 아키텍처까지 개발자의 시각에서 짚어보았다.

 

많은 회사들이 "우리는 지라를 쓰고 데일리 스탠드업을 하니까 애자일하게 일하고 있다."고 착각한다. 하지만 실무에서 느끼는 진짜 애자일은 요구사항 변경이라는 비즈니스의 당연한 본질을 시스템이 기술적으로 수용할 수 있을 때 비로소 완성된다.

 

 - 테스트 코드(TDD)없이 리팩토링할 수 없다.

 - 자동화된 파이프라인(CI/CD) 없이 빠르게 피드백받을 수 ㅇ없다.

 - 유연한 아키텍처(DIP, MSA) 없이 시스템을 안전하게 진화시킬 수 없다.

 

애자일 도입을 고민하고 있다면, 기획팀과 스크럼 프로세스를 정비하는 것만큼이나 개발팀의 기술 부채를 해결하고 엔지니어링 역량을 끌어올리는 일에 동등한 시간과 리소스를 투자해야 한다. 기술적 탁월함이 뒷받침 되지 않는 애자일은 결국 또 다른 형태의 껍데기일 뿐이다.

+ Recent posts