[기술 면접 기초 질문] Next Step 기술 면접 예상 질문

넥스트 스텝 이력서 / 면접 멘토링

기술 면접 예상 질문 / 답변

  • 자문자답입니다.
  • 질문의 순서는 무의미합니다.
  • 간결한 답변을 위해 생략된 부분이 있습니다.

운영체제

멀티 프로세스 방식과 멀티 스레드 방식의 차이는?

먼저 프로세스와 스레드의 정의를 짚고 가겠습니다.

  • 프로세스는 운영체제 내에서 하나의 프로그램이 실행되어 메모리에 적재된 상태, 작업 단위입니다.
  • 스레드는 프로세스 내에서 작업을 실행하는 작업 흐름의 단위입니다.

하나의 프로세스는 하나 이상의 스레드를 가지고 있습니다. 따라서 프로세스가 동작하면 적어도 1개 이상의 스레드가 이를 처리하게 됩니다. 두 방식의 차이점으로 먼저 프로세스는 서로 다른 프로세스 간의 메모리를 공유하지 않습니다. 물론 메모리를 공유하는 방법이 아예 없는 것은 아니나 번거롭고 성능 저하 등의 문제가 있습니다. 그래서 멀티 프로세스 간의 실행은 문맥 교환(Context Switching)에 들어가는 리소스가 많은 편입니다. 반면에 스레드는 하나의 프로세스 안에서 적정 범위의 메모리를 공유하게 됩니다. 이로 인해 스레드 간의 문맥 교환에 들어가는 리소스는 멀티 프로세스 환경 대비 적은 편입니다.

하지만 멀티 스레드 환경의 문제점은 하나의 스레드에서 발생한 문제가 공유 메모리와 다른 스레드에게 영향을 끼칠 수 있다는 점입니다. 예를 들어 자바는 이 멀티스레드 기능을 지원합니다. JVM 메모리 영역의 구조는 Heap Area, Stack Area, Method Area, PC Registers, Native Method Stacks으로 구성되어 있습니다. 여기서 Stack, PC register, Native Method 영역은 스레드 독립적이나 Heap, Method 영역은 스레드가 공유하는 영역입니다. 자바 프로그래밍에서 멀티 스레드 환경으로 개발을 진행하다 보면 공유되는 Heap 영역에 데이터로 인해서 메서드의 멱등성이 깨지게 됩니다. 이로 인해 데이터가 달라지는데 이를 추적, 디버깅하는 방법이 다소 까다롭습니다. 해결 방법으로는 synchronized 키워드를 통해 동기화를 제어하는 방법이 있습니다. 그래서 크롬 브라우저에서의 탭 기능은 멀티 스레드가 아닌 멀티 프로세스 환경으로 구현되어 있기도 합니다.


Java

자바의 클래스 로딩 절차는?

JVM에서 클래스 로딩 수행 절차는 크게 로딩(Loading), 링킹(Linking), 초기화(Initialization)로 나눠집니다. 로딩은 바이트 코드(클래스 파일)를 읽어 메서드 영역에 저장하는 기능이며, 링킹은 이를 검증, 실제 레퍼런스와 연결합니다. 이후 스태틱 클래스, 스태틱 변수 값 등을 초기화합니다.

클래스 로더

  • 런타임에 로딩 작업을 수행하는 모듈입니다.
  • 일반적으로 Bootstrap, Platform(Extension), System(Application)가 있습니다.
  • User-Defined은 사용자 정의 클래스 로더입니다(최하위 위치).

클래스 로더 속성

  • Hierarchical(계층적)
    • 클래스 로더는 상속 관계로 구성되어 있습니다.
    • Bootstrap(최상위) - Platform - System - User-Defined
  • Delegate(위임)
    • 로딩 요청을 최상위 로더에게 위임한 후 하위 로더로 내려오며 로딩이 수행됩니다.
  • Visibility(가시성)
    • 하위 클래스 로더는 상위 클래스 로더의 내용을 볼 수 있으나 반대로는 불가능합니다.
  • Uniqueness(유일성)
    • 상위 클래스 로더에서 클래스 로딩한 클래스를 하위 클래스 로더에서 다시 로딩하지 않게 합니다.

링킹의 절차

  • Verification(검증)
    • JVM에서 사용할 수 있는 유효한 바이너리 코드인지 검증합니다.
  • Preparation(준비)
    • Type이 필요로 하는 메모리를 할당합니다.
  • Resolution(링크 변경)
    • 심볼릭 레퍼런스를 실제 레퍼런스로 변경합니다.
    • 클래스 로더의 순서대로 실행 됩니다.

초기화

  • 스태틱 블럭 실행, 스태틱 변수의 값과 스태틱 클래스의 초기화 등을 진행합니다.

이러한 클래스 로딩 메커니즘이 활용된 실제 사례로는 spring-boot-devtools의 Automatic Restart 기능이 있습니다. 스프링 부트에서 제공하는 Devtools는 개발 시간 단축에 도움을 주는 도구입니다. 이를 사용하면 클래스 패스의 파일이 변경되는 경우 자동으로 재시작합니다. 스프링 부트는 기본적으로 Base Class Loader와 Restart Class Loader로 구성되어 있습니다. Base Class Loader는 기본적으로 바뀌지 않는 기본 클래스 로더입니다. Restart Class Loader는 Application에서 사용하는 일반적인 클래스 로더입니다. 이 경우 Restart Class Loader만 지우고 다시 생성하여 더 빠르게 프로젝트를 로딩시킬 수 있습니다. Base Class Loader는 영향을 받지 않기 때문에 스프링 부트 프로젝트를 직접 종료 후 재시작하는 것보다 빠릅니다.

스프링 부트 프레임워크는 상당히 무겁기 때문에 변경되지 않는 클래스들을 그대로 둔채 변경되는 클래스만 재로딩하는 것은 효율적이라고 생각합니다. DevTools는 자주 재시작을 해야 하는 개발 환경에서 개발 시간을 많이 줄여 개발 생산성 향상에 도움이 됩니다. 몰라도 되는 부분이지만 이러한 부분까지 고려하여 편의성을 제공하고 있다는 것에 대해 한 번 더 생각해보면 좋겠습니다.


자바의 애너테이션이란 무엇인가?

자바의 에너테이션은 메타데이터를 가리키는 일종의 주석입니다. @interface 형태로 애너테이션을 선언할 수 있고 설정에 따라 클래스, 메서드, 인스턴스 변수 등에 태깅할 수 있습니다. 애너테이션의 종류로는 표준 에너테이션, 메타 에너테이션, 마커 애네터이션 등이 있습니다. 애너테이션 선언 시 @Retention, @Target, @Repeatable 등 메타 애너테이션을 이용해 속성을 설정할 수 있습니다. 태그와 같이 말 그대로 특정 데이터임을 암시하거나 특정 작업을 처리하게끔 사용할 수 있습니다.

대표적인 활용 예시는 @Deprecated, @FunctionalInterface 등과 같은 표준 애너테이션이 있습니다. @Deprecated 애너테이션은 더는 사용되지 않음을 의미하며 사용시 주의를 의미하고 있습니다. @FunctionalInterface 애너테이션은 함수형 인터페이스를 가리키며 추상 메서드가 2개 이상일시 컴파일 에러를 야기합니다.

개인적으로는 테스트 용도의 StopWatch 기능을 스프링 부트의 AOP(애너테이션)를 활용해 구현한 적이 있습니다. 특정 메서드에 해당 애너테이션을 태깅하여 메서드 처리 시간을 측정하는 기능이었습니다. 예를 들면 아래와 같은 방식으로 애너테이션과 해당 애너테이션을 처리하는 Aspect 클래스를 작성하였습니다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface StopWatch {
}

@Aspect
@Component
public class LoggingAspect {

    @Around("@annotation(StopWatch)")
    public Object stopWatch(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();

        String className = joinPoint.getSignature().getName();

        try {
            stopWatch.start();
            Object proceed = joinPoint.proceed();
            stopWatch.stop();
            LOGGER.info("elapsed time :: {}", stopWatch.getTotalTimeMillis());
            
            return prceed;
        }

        return null;
    }
}

여기서 @Aspect는 해당 객체가 흩어진 관심사를 모아둔 모듈(횡단으로 실행할 로직)임을 의미하며 @Around는 대상 객체를 설정합니다. joinPoint.proceed(); 를 전후로 스탑워치 시작과 종료 기능을 호출하여 시간을 측정 후 출력시키는 방식으로 구현하였습니다.

애너테이션을 사용하면 간편하게 구현할 수 있고, 구현 코드가 짧아진다는 장점이 있습니다. 하지만 당연히 단점도 있습니다. 첫 번째로 애너테이션 사용은 리플렉션 처리로 인해 파일 로딩에 성능 부분에 오버해드가 존재합니다. 두 번째로 XML 파일 사용은 리컴파일이 필요 없는데 반해 애너테이션 사용은 리컴파일이 필요하다는 단점이 있습니다. 그리고 프로젝트 전반에 대한 설정 등은 애너테이션으로 설정할 수 없습니다. 이 경우 XML 파일을 사용해야 합니다.

따라서 개발 환경에 의존하여 자주 변경될 가능성이 있는 정보는 XML, 앱 내에 런타임에 결정되는 정보는 애너테이션을 사용하는 등 이러한 장단점을 잘 이용한다면 개발 생산성을 높여줄 것입니다.


데이터베이스

DB 인덱스란?

데이터베이스에서 테이블의 데이터 핸들링 시 작업 속도를 향상하게 시켜주는 자료구조, 기능입니다. 인덱스는 하나 이상의 컬럼(열)을 사용하여 생성 가능하며, 레코드의 빠른 조회 등을 위한 기반을 제공합니다. 일반적으로 인덱스는 데이터 조회처리에서 성능 향상을 목적으로 하며 삽입, 수정, 삭제 등의 쿼리는 성능이 저하됩니다. 하지만 수정, 삭제 시 조회 부분이 인덱스를 사용한다면 데이터를 찾는 것 자체는 빠를 것입니다. 다시 말해 인덱스는 조회 성능을 위해 메모리 영역에 목차를 생성하는 것이라 볼 수 있습니다. 인덱스를 선정할 때는 중복 값이 적은, 즉 카디널리티가 높은 컬럼을 인덱스로 선정하는 것이 좋습니다.

데이터베이스의 벤더, 버전, 스키마 구조 등과 같은 환경 조건마다 다르겠지만 일반적으로 조회 조건이 없거나 인덱스가 아닌 컬럼을 조건으로 조회한다면 조회 쿼리의 실행계획은 풀 테이블 스캔일 것입니다. 이런 경우 데이터가 적은 경우엔 괜찮겠지만, 데이터가 수백만 건이 넘는 경우라면 성능에 지장을 받게 됩니다.

실제로 하나의 잘못된 조회 쿼리로 인해 동시간대에 실행되는 같은 쿼리들이 성능에 영향을 받아본 경험이 있습니다. 인덱스를 타지 않고 조회되는 쿼리로 인해 슬로우 쿼리가 되었고 이는 데이터베이스 시스템 전체 성능에 영향을 주게 되었습니다. 올바른 컬럼을 선정하여 인덱스를 생성한 후 쿼리를 실행하니 실행 시간은 1초 이내로 줄어들게 되었습니다.

하지만 무분별하게 인덱스를 많이 생성하는 것이 좋은 것은 아닙니다. 무분별하게 불필요한 많은 인덱스를 생성할 시 데이터 핸들링에 있어 성능 저하를 가져올 수 있습니다. 또한 이미 언급했듯 인덱스는 조회 쿼리의 성능 향상과 삽입, 수정, 삭제 등 쿼리의 성능 저하를 맞바꾸는 것입니다. 따라서 상황과 스키마 구조에 따라 적절한 컬럼을 선택하여 인덱스를 생성하는 것이 좋습니다.


네트워크

HTTP와 HTTPS 프로토콜의 차이는 무엇인가?

먼저 HTTP는 Hyper Text Transfer Protocol의 약자로 WWW 환경에서 하이퍼링크와 같은 데이터를 송수신하는데 사용되는 통신 규약입니다. 주로 TCP 프로토콜이 사용되나 HTTP 3 버전 이후에는 UDP 프로토콜이 사용되고 있습니다. 그리고 이 HTTP 프로토콜에는 보안 취약점이 존재합니다. 서버와 클라이언트(브라우저) 간 전송되는 데이터가 암호화되지 않는다는 것입니다. 따라서 이를 보완하기 위해 나온 프로토콜이 HTTPS (HTTP + Secure)입니다. HTTPS 인증과 암호를 강화하기 위해 SSL/TLS 프로토콜을 사용하여 세션 데이터를 암호화합니다. HTTPS는 전자 상거래 등과 같이 보안이 중요한 경우에 사용됩니다.

HTTPS는 대칭키와, 공개키-개인키를 모두 사용합니다.

대칭키 방식

  • 서버와 클라이언트 모두 같은 키(대칭키)를 사용하여 데이터를 암호화, 복호화합니다.
  • 대칭키 하나로 암호화, 복호화를 모두 수행하기 때문에 데이터를 송수신하는 양측 모두 대칭키를 알고 있어야 합니다.
  • 공개키-개인키 방식에 비해서 성능상 빠르나 외부에 대칭키가 노출되면 위험합니다.

공개키-개인키 방식

  • 공개키와 개인키는 하나의 키 쌍이며 서버와 클라이언트 모두 각각 공개키와 개인키를 갖고 있습니다.
  • 공개키는 외부에 공개하지만 개인키는 절대 외부에 공개하거나 노출되면 보안상 위험합니다.
  • 특정 데이터를 공개키로 암호화할 경우 공개키의 쌍인 비밀키로만 복호화가 가능합니다.
    • 반대로 비밀키로 암호화한 경우 비밀키의 쌍인 공개키로만 복호화가 가능합니다.
  • 대칭키 방식보다 성능상 느립니다.

HTTPS는 이 방식들을 함께 사용해 각 방식의 단점을 상쇄하고 있습니다. 일반적으로 서버의 공개키에는 인증기관(CA)의 인증을 보장하는 전자서명(인증기관의 개인키로 암호화된 인증서)이 포함되어 있습니다. 따라서 서버는 이 인증서를 클라이언트(브라우저)에게 송신하고 클라이언트는 이 키를 인증기관의 공개키로 복호화하여 확인합니다. 클라이언트는 이후에 사용할 대칭키를 해당 인증서에 포함된 서버의 공개키로 암호화해 전송합니다. 이를 수신한 서버는 서버의 개인키를 이용해 클라이언트의 데이터를 복호화하여 클라이언트가 전송한 대칭키를 확보합니다. 이후부터는 서버와 클라이언트는 이 대칭키를 이용해 데이터를 주고 받습니다. 결론적으로 HTTP와 HTTPS의 차이는 보안성이라고 할 수 있겠습니다. 보안 절차로 인해 HTTPS가 HTTP보다 성능이 다소 떨어질 수 있으나 이는 최근에 사람이 느끼지 못할 정도입니다. 하여 보안적으로 중요한 데이터를 다룬다면 HTTP보다 HTTPS 프로토콜을 사용하는 것이 좋습니다.


REST API의 정의는? 그리고 REST API 설계시 고려사항은?

먼저 REST란 Representational State Transfer의 약자로 WWW 같은 분산 시스템 환경을 위한 소프트웨어 아키텍처 중 하나입니다. REST 아키텍처는 네트워크 아키텍처의 모음이라고 볼 수 있기 때문에 하이브리드 아키텍처라고도 불립니다. REST는 URI(URL, URN) 형태로 리소스(자원, 서비스되는 기능)를 제공하는 특성이 있습니다. 일반적으로 HTTP 프로토콜을 사용하나 반드시 HTTP 프로토콜을 사용할 필요는 없습니다. REST 아키텍처의 가이드 조건을 모두 만족한 상태를 RESTful이라고 하며 가이드 조건은 약 6가지가 있습니다.

REST 아키텍처의 특징(조건)

  1. Server-Client 구조
    • 서비스를 요청하는 클라이언트와 리소스를 제공하는 서버 구조로 이루어져 있습니다.
  2. Stateless
    • 콘텍스트와 무관하여 순서나 절차가 상관없습니다.
  3. Cacheable
    • 기존 웹 환경에서처럼 캐싱 처리를 할 수 있어야 합니다.
  4. Layered Architecture System
    • 외부에서는 서버의 레이어를 알 수 없습니다.
    • 로드밸런서를 중간에 배치하는 등 서버 구조의 계층화를 통해 서버의 확장성과 유연성을 확보할 수 있습니다.
  5. Uniform Interface
    • 일관적인 인터페이스로 분리되어야 함을 뜻합니다.
  6. Code on Demand(Optional - 필수 아님)
    • 서버가 클라이언트가 실행 가능한 소스(로직)를 제공해 기능을 확장 시킬 수 있습니다.

API는 Application Programming Interface의 약자로 구현된 기능을 외부에서 사용할 수 있게 제공되는 인터페이스입니다. 정리하면 REST API는 REST 아키텍처의 가이드 형태를 따르는 API라고 할 수 있습니다. 가이드를 따르지 않는다면 REST API보다는 HTTP API에 더 가깝습니다. REST API는 리소스(URI), 행위(HTTP method), 표현(Representation) 구조로 이루어져 있습니다. REST API 설계를 위해서는 먼저 REST API 원칙을 준수해야 합니다.

REST 인터페이스(API) 원칙

  1. 자원의 식별 (Resource identification in request)
    • 요청 내에 개별 자원이 식별 가능해야 합니다.
  2. 메시지를 통한 리소스의 조작 (Resource manipulation through representations)
    • 메시지가 충분한 메타데이터를 포함한 경우 메시지를 통하여 자원을 조작합니다.
  3. 자기 서술적 메시지 (Self-descriptive message)
    • 각 메시지는 자신을 처리하는 정보를 충분히 포함해야 합니다.
  4. 애플리케이션의 상태에 대한 엔진으로서 하이퍼미디어 (HATEOAS)
    • Hypermedia As The Engine Of Application State
    • 연관된 리소스의 자원 상태를 제공합니다.

위 원칙을 기반으로 REST API 설계 시 고려 사항은 꽤 많은 양이라 간략하게 말씀드립니다.

  1. URI Rules
    • URI는 소문자를 사용해야 하고, 명사를 사용하는 것이 바람직합니다.
    • 또한 ‘_’ 대신 ‘-‘를 사용하는 것이 좋습니다.
  2. HTTP Headers
    • Content-Location를 사용해 실제 위치를 명시하거나 HATEOAS를 사용합니다.
    • Content-type은 application/json를 제공합니다.
    • 비정상적인 요청이 많은 경우 Retry-After를 사용해 429(Too Many Request)를 응답합니다.
  3. HTTP Method
    • 기본적으로 POST, PUT, GET, DELETE 메서드를 제공합니다.
  4. HTTP Status Code
    • 응답시 API 처리 결과에 따라 알맞는 HTTP 상태 코드를 담아 반환합니다.
  5. HATEOAS (Hypermedia As The Engine Of Application State)
    • 리소스의 상태 전이 링크를 제공합니다.
  6. Paging, Ordering, Filtering, Field-Selecting
    • 페이징, 정렬, 조건 필터, 특정 컬럼만 조회 등과 같은 기능을 제공합니다.
  7. Versioning
    • URI 버저닝을 합니다.
  8. 기타

REST API를 사용하는 것은 여러 장점이 있습니다. 일반적으로 HTTP 프로토콜을 사용하기 때문에 어렵지 않게 사용할 수 있고 콘텍스트를 보관할 필요가 없습니다. 또한 xml, json 등 어떠한 representation 형태로 표현이 가능합니다. 하지만 공식적인 가이드를 제공하지 않고 강제성을 띠지 않기 때문에 명확한 기준점, 표준이 없습니다. 이로 인해 프로젝트에서 API 설계에 다양한 사람들이 투입되거나 명확한 가이드라인을 규정하지 않는다면 다소 난항을 겪게 됩니다. API는 말그대로 외부에 공개되는 인터페이스로써 한 번 공개되면 변경이 어렵기 때문에 처음부터 명확한 기준을 가지고 설계가 이루어져야 합니다.


프록시 서버의 정의와 역할은?

프록시 서버는 클라이언트가 외부 통신망, 네트워크 서비스에 접속할 때 간접적으로 접속하거나, 또는 부가 기능 수행이나 대리 수행해주는 프로그램을 내포하는 서버를 말합니다. 혹은 포괄적으로 그 수행 프로그램, 시스템을 표현하기도 합니다. 일반적으로 데이터를 대신 전달하는 기능을 수행하며 보안, 성능 향상 등의 목적을 가지고 있습니다. 그래서 요청 리소스 캐싱 기능, 특정 IP 영역대 차단, 트래픽 분산 등의 기능을 수행하는 경우가 많습니다.

이 프록시 서버의 종류로는 포워드 프록시 서버와 리버스 프록시 서버가 있습니다. 일반적으로 요청 방향에 따라 나뉘고 있습니다. 내부 네트워크에서 외부 네크워크로 나가기 전에 거치는 프록시 서버가 포워드 프록시 서버이며 외부 네트워크에서 내부 네트워크로 들어오기 전에 거치는 프록시 서버가 리버스 프록시 서버입니다.

포워드 프록시 서버는 데이터를 캐싱하여 요청에 대한 처리를 할 수 있습니다. 그리고 외부 네트워크로부터 내부 클라이언트(호스트)의 정보를 숨기는 역할도 수행할 수 있습니다. 리버스 프록시 서버는 외부네트워크에서 접근시 유효한 IP 영역대 인지 확인하는 보안 기능을 수행할 수 있습니다. 또한 대규모 트래픽을 감당해야 하는 서비스인 경우 들어오는 요청 트래픽을 감별하여 부하를 각 서버에게 분산처리해 줄 수 있습니다.

이처럼 프록시 서버는 네트워크 송수신시 클라이언트와 서버의 기능을 보완하거나 안정적인 상태를 보장할 수 있도록 도와주는 역할을 하고 있습니다.


설계 원칙, 프로그래밍 패러다임 (OOP, FP)

SOLID 원칙이란?

객체지향 디자인을 위한 원칙 중 1개입니다. 총 5가지로 이루어져 있습니다.

  1. 단일 책임 원칙 (Single Responsibility Principle)
    • 클래스는 단 한 가지의 책임만을 가져야 하며 수정 이유는 단 한 가지여야 한다는 원칙입니다.
    • 책임 분산 시 유연하고 깔끔한 코드를 구현할 수 있습니다.
  2. 개방 폐쇄 원칙 (Open Close Principle)
    • 개체는 확장엔 열려있고 수정엔 닫혀 있어야 한다는 원칙입니다.
    • 말 그대로 수정시 영향을 최소화하고 확장은 쉬워야 합니다.
  3. 리스코프 치환 원칙 (Liskov Substitution Principle)
    • 서브타입은 그것의 수퍼타입으로 치환 가능해야 한다는 원칙입니다.
    • 서브타입은 수퍼타입의 기능을 변경하지 않고 확장만 하는 수행해야 합니다.
  4. 인터페이스 분리 원칙 (Interface Segregation Principle)
    • 클라이언트가 자신이 사용하지 않는 메서드에 의존하도록 강제되어선 안 된다는 원칙입니다.
    • 따라서 인터페이스를 책임별로 잘게 분리하여야 합니다.
  5. 의존 관계 역전 원칙 (Dependency Inversion Principle)
    • 상위 수준 모듈이 하위 수준의 모듈에게 의존하지 않아야 한다는 원칙입니다.
    • 구체화에 의존하지않고 추상화에 의존해야 합니다.

실제로 해당 원칙의 중요성을 느끼게 된 경험이 있습니다. 당시 언론 매체별 기사 데이터를 특정 데이터 형식으로 파싱하는 기능을 구현했습니다. 이때 Article이라는 객체에 파싱 기능이 구현되어 있고 파싱 후에 데이터를 갖고 있는 구조 였습니다. 하지만 시간이 지날수록 매체가 늘어나기 시작했고, 매체마다 다른 파싱작업이 필요했습니다. 문제는 이 구조가 위 솔리드 원칙에서 단일 책임 원칙과 개방 폐쇄 원칙을 지키지 못하고 있었습니다. 기존에는 파싱 작업 내에서 기사 특성을 분기 처리하여 파싱하는 방식으로 처리되어 있었습니다. 이로 인해 파싱 기능을 수정할 때 다른 기사의 파싱 작업까지 변경의 영향을 끼치는 인적 장애가 발생하곤 했습니다. 해결을 위해 솔리드 원칙에서도 단일 책임 원칙과 개방 폐쇄 원칙이 지켜지게끔 클래스 아키텍처를 수정하였고 Article이라는 기사 객체의 파싱 기능을 Parser 객체로 분리, 합성 구조로 구현했습니다. 변경 후부터는 유연하고 안정적인 구조가 되어 기능 수정 시에도 기존 기능이 영향을 받는 일이 없어졌습니다.

솔리드 원칙이 중요한 이유는 유연하고 안정적인 설계를 위해서입니다. 안전하면서도 빠른 기능 반영이 요구되는 애자일 프로세스 내에서라면 더욱 중요하다고 생각합니다. 물론 설계 원칙들을 상황과 구조에 따라 지키지 못하는 경우도 있습니다. 다만 가능한 경우 이러한 원칙을 지킨다면 유연하고 안정적인 설계에 도움이 된다고 생각합니다.


디미터 (디미테르) 법칙이란?

디미터 법칙은 협력 객체들의 경로를 제한하여 객체간 결합도를 낮추기 위한 법칙 중 하나입니다. 특정 객체의 메서드에서 호출할 수 있는 메서드를 논리적으로 제한하는 것이 이 법칙의 특징입니다.

객체 O의 메서드 m에서 호출할 수 있는 메서드의 종류

  1. O 객체 자신(this)의 메서드
  2. m 메서드가 생성한 객체의 메서드
  3. m 메서드의 파라미터로 넘어온 객체의 메서드
  4. O 객체가 필드로 가지고 있는 인스턴스 변수(객체)의 메서드
  5. m 메서드에서 접근 가능한 전역 변수(객체)의 메서드

이 법칙 또한 다른 설계 원칙들과 마찬가지로 반드시 지키지 않아도 되나, 가능하면 지키는 것이 유연한 설계의 지름길입니다. 객체간 결합도가 높다면 특정 객체를 수정했을 때 영향을 받게 되는 객체들이 많아져 사이드 이펙트가 발생할 확률이 높아집니다. 더욱이 결합하는 객체가 인접한 객체가 아닌 경우 그 영향에 여파를 가늠하기 더 힘들어집니다. 따라서 처음 설계, 구현 시부터 디미터의 법칙을 지켜 거리가 먼 객체 간의 결합도를 최대한 낮추는 것이 바람직하다고 생각합니다.


CQRS 패턴이란?

CQRS은 Command Query Responsibility Segregation의 약자로 커맨드(명령)와 쿼리(조회)의 책임을 분산하는 아키텍처 패턴입니다. 커맨드와 쿼리를 분리하는 프로그래밍 원칙인 CQS에서 파생된 아키텍처 패턴이라는 설이 유력합니다. 여기서 커맨드는 데이터의 변경을 발생시키는 행위이며 쿼리는 데이터를 가져오는 행위입니다. 이 패턴에서 커맨드는 데이터 자체가 아닌 행위를 기반으로 해야 합니다. 그리고 쿼리는 데이터를 수정하지 않는 행위여야 합니다.

애플리케이션을 서비스하다 보면 시간이 지남에 따라 규모가 커지면서 도메인이 복잡해집니다. 이를 위해 커맨드와 쿼리를 분리하여 개발, 운영하는 아키텍처 패턴 적용을 생각하게 되는데 이 중 하나가 CQRS 패턴입니다. 애플리케이션 내에서 커맨드와 쿼리를 기준으로 도메인을 분리하여 구현 및 개발하는 방식입니다. 설계에 따라 데이터 저장소를 물리적으로 커맨드 트랜잭션 전용 저장소와 쿼리 전용 저장소를 나누어 구현하기도 합니다. 이런 경우 두 데이터 저장소의 일관성이 깨지기 때문에 데이터를 동기화 시켜주는 작업이 필요합니다. 일반적으로 RabbitMQ, kafka 같은 메시지 브로커를 통해 데이터 동기화 작업을 수행합니다.

물론 현대 서비스에서는 명령과 쿼리로 나누기 애매한 작업들이 있습니다. 또한 CQRS 아키텍처 패턴은 도메인 분리로 인해 기능적으로 안정적인 구현을 할 수 있으나 구현해야 되는 코드가 많아지고 더 많은 기술을 적용하여 관리해야 하는 등과 같은 단점도 지니고 있습니다. 따라서 무작정 해당 아키텍처 패턴을 적용하는 것이 아닌 수행하는 프로젝트의 도메인과 환경을 보고 도입 여부를 결정하는 것이 좋을 것입니다.


댓글남기기