[MSA] (1) 내가 MSA를 도입한 이유

이 글은 왜 필자가 현재 개발하고 있는 프로젝트와 서비스들을 MSA 기반으로 설계하려고 했는지와 그때의 고민 그리고 현재 상황에 대한 기록이다.

MSA에 대하여

MSA( Micro Service Architecture )는 요즘은 크고 거대한 시스템을 구축할 때에 당연히 고려할 만큼 많이 알려진 방법이다. 이와 반대점에 서 있는 Monolithic Architecture 가 갖고 있는 장점도 분명 있으나, 어떤 시스템이 확장( Extend ) 되면서 증가하는 코드 복잡성 때문에 개발시간은 점점 길어지고, 거대한 어플리케이션은 운영 ( Production ) 환경에 배포하는 과정도 길어질 수 밖에 없다. 또한 복잡하고 어려운 코드를 유지하기 위한 수많은 테스트코드와 A4 용지 한장은 충분히 넘을것 같이 많은 의존성( Dependecies ) 모듈까지, 시간이 지나면 지날 수록 새로운 기술스택을 받아들이기 어려워진다.

하지만 이미 기존에 개발되어 있는 서비스, 혹은 시스템을 MSA로 재설계하는건 많은 비용이 든다. 코드를 새로 개발해야하고, 리팩토링 하는 과정에서 생길 수 있는 여러 이슈들도 있고, 무엇보다 인적자원이 많이 들어가기 때문에 기존에 작성된 코드가 많으면 많을수록 당연하게 비용은 점점 커진다.
실제로, 필자가 처음 개발하고 유지보수해야 했던 Backend 서비스는 핵심 도메인 기능을 가진 어떤 함수 하나가 400줄이 넘는 코드를 갖고 있어 처음작동 흐름( Flow )을 이해하는 데에 정말 오래 걸렸다. 예를 들면, 코드가 이런 식이었다.

// 특정 Pod 를 실행해야 한다.
    public Mono<Object> runner() {

        //권한을 확인한다.
        return this.repository.findRole(User).flatMap(user -> {
            boolean hasRole = User.roleCheck();

            String nameSpace = k8sController.getNamespace();
            V1Pod pod = ...

            JSONObject result = new JSONObject();

            //DB에서 데이터 조회 후 결과 확인..
            return this.repository.findByPodId().flatMap(entity -> {

                boolean isPodRunning = entity.get..
                if (entity.value.equals())...
                //Kubernetes 에 파드 배포..
                return this.k8sController.apply(new Pod).flatMap(pod -> {
                    if (pod.isOk()) {

                    }
                    ...
                    //올바르게 작동하면 설정값 주입,
                    return this.client.injectConfig().flatMap(config -> {
                        //올바르게 들어갔으면..
                        if (pod.isOk()) {
                            return this.repository.updatePodStatus().flatMap(entity -> {
                                //정보 업데이트하고, 파일도 업로드 하고 ..
                                if(isOk()) uploadFile ..
                                //그리고 생성된 파드와 통신객체 만들어 연결하고..
                                return this.connectClient.connect().flatMap ....

                            })
                        } else throw new RuntimeException();
                    })
                })
            })
        })
    }

근본적인 문제는 이 코드를 보고 이 함수가 무슨일을 하는지 알기가 너무 어렵다는것이다. 물론, 시간은 오래걸렸으나 핵심 도메인 로직이 이 함수하나에 모두 다 적혀있었기에 이해하고 나서는 도메인에서 필요한 핵심을 다 이해할 수 있다는 장점(?) 은 있었다.
물론 위 코드는 WebFlux 에서 flatMap 과 map 을 체이닝 하는 방법도 잘못되었고, 캡슐화에 대한 문제도 있었지만, 이를 근본적으로 모듈화와 캡슐화를 통해 보기좋게 만들어도, flatMap 을 좀 더 가독성 있게 리팩토링 해도, 결국 가장 큰 문제는 이 함수를 포함하고 있는 Application 이 Database 관리, Kubernetes 관리, 유저 관리와 인증… 등등 너무 많은 일을 하고 있기 때문에 점점 복잡해졌으면 복잡해졌지, 쉬워지지 않는다는것이었다.
이후에 개발했던 서비스는 Python 기반이었는데, 이 친구는 더 심했다. 꽤나 큰 서비스를 담당하고 있었는데, 놀랍게도 딱 하나의 서비스가 모든 일을 다 처리하고 있었다. 필자는 단편적으로 내가 개발한 부분만 기억할 뿐, 아직도 그 어플리케이션이 무슨일을 하는지, 무슨 기능이 있는지 모른다.

필자는 이런 상태에서 어플리케이션을 계속해서 개발해 나가는것 또한 옳지 않다고 생각한다. 덮어두고 지나가는건 언젠가 문제가 생길 수 있다는 뜻이니까. 그래서 가장 좋은 방법은 처음 설계할 때 부터 서비스의 경계를 정하고 ( Bounded ) 그 경계를 넘어가는 것이 필요하다면 이를 분리해야한다고 생각한다.

MSA를 선택한 이유.

서비스를 개발하는 프레임워크와 언어에 자유가 생긴다는 것도 굉장한 이점이라고 생각한다. 실제로 새로 개발했던 신규 프로젝트는 인원은 넉넉하지 않은데, 기한은 짧은 시간이 굉장히 촉박한 프로젝트였다. 그런데 나는 Java로 개발하는 것이 익숙하지만, 함께 협업해야 하는 다른 개발자는 Java 개발에 익숙하지 않은 사람이었다. 하지만 반대로 나는 Python 을 아주 잘 다루지는 못하지만 그 사람은 Python을 잘 다루는 사람이었다.
하나의 언어와 프레임워크로만 개발해야 했다면 잘 모르는쪽이 밤을 새워가며 언어공부와 개발을 병행해야 할 것이다. 하지만 이런 상황에서 도메인에 필요한 서비스를 분리하고, 나는 Java로, 그리고 협업하는 팀원은 Python으로 각각의 서비스를 구현하고, 이 서비스들을 Kafka 로 이어 줌으로써 빠르게 구현할 수 있었다.
또한, 이후에 새로 서비스를 확장하는 경우에도 기존에 만들어 둔 Kubernetes 시스템에 새로 웹 서버를 개발해서 Kafka 와 이어주기만 하면 완성된다. 이런 경험으로부터 필자는 MSA로 개발하는 게 훨씬 더 많은 생산성을 갖는다 생각하고, 신규 개발하는 경우에는 언제나 마이크로 서비스로 전환할 수 있게끔 항상 생각하려 한다.

어려운 MSA – 분산 트랜잭션, Monitoring, 회로 차단 패턴…

MSA를 실제로 잘 구현해 내려면 분산 트랜잭션이나 모니터링, 회로 차단 패턴과 같은 난이도 있는 개념을 충분히 학습하여야 하고, 안정적으로 서비스하기 위해서는 수많은 기술이 필요하다.
그럼에도 불구하고, 필자는 MSA가 갖는 장점과 이점이 크다고 생각하기 때문에 앞으로 천천히 배워가며 개발한 것들을 천천히 기록으로 남길 것이다.

다음에 이어질 글은 MSA를 구현하기 위한 시스템과 Spring framework 에서 많이 쓰이는 두 프레임워크 Axon & Eventuate Tram 에 대해 작성해본다.

Leave a Comment