책 <클린아키텍처>의 [Part4 컴포넌트의 원칙]을 참조하여 재가공한 내용입니다.

책의 컴포넌트와 관련된 내용들을 제 입맛에 맞게 많이 빼고 더했습니다. 먼저 컴포넌트의 간략한 역사를 살펴보고, 그 설계 원칙과 관련된 내용들을 컴포넌트 그 내부와 관련된 내용과 컴포넌트간의 관계에 대한 내용으로 구분하여 정리하였습니다.

이 글에서는 먼저 컴포넌트의 정의와 (컴포넌트)플러그인 아키텍처를 간략히 이해하고 플러그인 아키텍처의 기원을 살펴보면서 컴포넌트가 탄생한 배경을 같이 설명하겠습니다.

컴포넌트 정의

컴포넌트는 배포단위다. 컴포넌트는 시스템의 구성 요소로 배포할 수 있는 가장 작은 단위다.

간단히 생각해서 컴포넌트는 독립적인 배포가 가능한 단위라고 생각할 수 있습니다. 컴포넌트의 구체적인 형태는 컴파일형 언어의 경우 바이너리 파일의 결합체(Java - jar, Ruby - gem, .NET - DLL)이고 인터프리터형 언어의 경우 소스파일의 결합체입니다.

(컴포넌트) 플러그인 아키텍처

컴포넌트라는 단위의 기원을 거슬러 올라가다보면 플러그인 아키텍처가 탄생한 배경과 겹쳐져 있습니다.
책에 플러그인 아키텍처가 명시되어 있으나 사실상 자세한 설명이 없고, 웹 상에서도 직접적으로 플러그인 아키텍처를 설명해주는 글을 찾지 못하여 이클립스의 아키텍처를 살펴보는 것으로 그 내용을 대신합니다.

위 다이어그램은 대표적인 플러그인 아키텍처가 적용된 Eclipse의 개념도입니다.
위 다이어그램에서 보여지는 바와 같이 Eclipse는 그 핵심적인 기능은 Eclipse Platform이라는 하나의 컴포넌트 안에 구현이 되어 있고, 추가 기능들은 새로운 컴포넌트를 전기 코드 꼽 듯이 기존 컴포넌트에 이어 붙이는 형태로 구현할 수 있습니다. 이렇게 새로운 컴포넌트를 추가하는 것을 용이하게 하는 것을 플러그인 아키텍처라고 추정해 볼 수 있습니다.

플러그인 아키텍처의 역사(탄생 배경)

플러그인 아키텍처가 탄생하기 까지 그 설계의 성장 시기를 크게 3가지 시기로 나누어 볼 수 있습니다.

  • 소프트웨어 개발 초창기
  • 링킹로더의 탄생기
  • 컴퓨팅 속도의 성숙기

소프트웨어 개발 초창기

        *200
        TLS
START,     CLA
        TAD BUFR
        JMS GETSTR
        CLA
        TAD BUFR
        FMS PUTSTR
        FMP START
BUFR,    3000

GETSTR,    0
        DCA PTR
NXTCH,    KSF

(후략...)

위 코드는 (일부 생략되었지만) 키보드로부터 입력을 받아 버퍼에 저장하고, 이를 테스트하는 코드가 포함된 PDP-9 프로그램이라고 합니다. 여기서 주목할 것은 첫 줄에 쓰여 있는 "*200"이라는 코드입니다. 이는 200이라는 메모리 주소에 로드할 코드를 생성하라는 명령이라고 합니다. 이렇듯 소프트웨어 개발 초기에는 해당 소프트웨어가 로드 될 메모리의 주소를 개발자가 직접 입력해야 했습니다.
그리고 외부 라이브러리 함수의 경우 어플리케이션 코드에 포함시켜 컴파일을 시켰는데, 당시 메모리 자원이 비싸고 한정적이였던 특성상 그 라이브러리가 방대할 경우에는 컴파일 시간이 오래 걸렸다고 합니다. 그래서 고안한 방법이 외부 라이브러리와 어플리케이션을 각각 따로 메모리에 로드시키는 방식이었습니다. 예를 들면 아래와 같은 형태입니다.

메모리 주소 0번에 어플리케이션 코드를 로드하고, 메모리 주소 2000번에 외부 라이브러리 코드를 로드하는 형태입니다. 이와 같이 어플리케이션을 구동할 경우 외부 라이브러리를 먼저 로드하고 해당 주소와 관련된 정보를 어플리케이션 코드가 컴파일 되는 순간에 전달하고, 이후 컴파일된 바이너리를 실행하는 순서로 프로그램을 동작시켜야 했다고 합니다.

링킹로더의 탄생기

그런데 여기서 어플리케이션 코드가 추가될 경우 문제가 발생합니다.

어플리케이션 프로그램의 메모리 주소가 위와 같이 분절되기 때문입니다.

이를 해결하기 위해서 로드하는 순간에 메모리주소를 결정할 수 있는 로더가 탄생하게 됩니다. 그러면 어플리케이션 코드가 로드되는 순간에 어플리케이션의 메모리 주소와 외부 라이브러리의 주소를 결정할 수 있게 됩니다. 각각 다른 메모리 영역에 로드되기 때문에 이 둘을 연결(Linking)하는 작업도 이어집니다. 그래서 이 때 사용되는 프로그램을 링킹 로더라고 부르게 됩니다.
이 때부터 프러그래머는 프로그램을 개별적으로 컴파일하고 로드할 수 있게 됩니다. 이렇게 컴포넌트라는 개념이 형성되기 시작합니다.

컴퓨팅 속도의 성숙기

그런데 아직 플러그인 아키텍처에 도달하기 위해서는 한 가지가 남아있습니다. 그 것은 동적링크입니다. 링킹로더가 막 탄생했을 시기(약 1980년대 후반 까지)만 해도 아직 컴퓨팅 자원이 한정적이였을 때이고, 컴파일과 로드, 링크를 처리하는데 꽤 많은 시간이 소요되었습니다. 그러나 무어의 법칙이 등장한 이후 컴퓨팅 속도는 빠르게 증가하였고, 동적으로 프로그램을 로드하고 링크하는 것이 가능할 정도로 컴퓨팅 속도가 성숙하게 되었습니다. 이렇게 탄생하게 된것이 (컴포넌트) 플러그인 아키텍처 입니다.

다음 글에서 컴포넌트 설계 원칙에 대해서 다루도록 하겠습니다.

minIO 개요

minIO는 하이브리드 클라우드에 걸맞는 객체 스토리지입니다.
minIO는 쿠버네티스-네이티브 객체 스토리지이며, 하이브리드 클라우드의 수요에 맞춰 구축되었습니다. 따라서 쿠버네티스 환경에서 일관된 경험을 제공합니다. 또한 minIO는 산업을 리드하는 성능과 확장성을 제공하며, 다양한 사례(AI/ML, 백업저장소, 웹 또는 모바일 어플리케이션)에 적용될 수 있습니다.

Object Storage for theEra of the Hybrid Cloud

MinIO’s high performance, Kubernetes-native object storage suite is built for the demands of the hybrid cloud. Software-defined, it deliversa consistent experience across every Kubernetes environment.

minIO와 하이브리드 클라우드

minIO는 하이브리드 클라우드 전략하에서 적절하게 사용될 수 있는 객체 스토리지입니다. 현재 minIO는 수백만개의 인스턴스가 프라이빗 클라우드 위에 동작하며, 이 뿐만 아니라 수천만개의 인스턴스가 AWS, Azure, GCP위에서 동작하고 있습니다. 이러한 minIO는 AWS의 S3와 호환이 가능합니다.
minIO는 다른 어떤 객체 스토리지보다 클라우드 환경에 친화적이고, 쿠버네티스 환경에서 containerization, orchestration 등이 가능합니다.

minIO의 성능

minIO는 세계에서 가장 빠른 객체 스토리지이며, 읽고 쓰는 속도는 각각 183GB/s, 171GB/s입니다.

minIO의 확장성

minIO는 간단한 확장 모델을 사용합니다. 여러개의 minIO인스턴스는 하나의 클러스터로 묶이고, 이러한 클러스터들은 다시 글로벌 네임스페이스를 생성하며 병합될 수 있습니다.

minIO와 오픈소스

minIO는 사용자가 변경 및 락인하는 것이 가능합니다.

minIO기능과 아키텍처

minIO의 기능

minIO-엔터프라이즈 버전의 기능은 객체 스토리지의 표준을 의미한다.

Erasure Coding

minIO는 어셈블리 언어로 작성된 Erasure Coding을 통해 데이터를 보호합니다. minIO는 Reed-Solomon알고리즘을 사용합니다. 알고리즘과 관련된 자세한 내용은 정확히 이해하지 못했지만 .. 대략 여러 블럭으로 쪼개진 데이터 중 많은 부분이 손실되어도 복구가 가능하다고 합니다. 결론은 minIO는 오브젝트 수준에서 데이터 복구가 각각 가능하다고 합니다.

Bitort Protection

디스크 드라이브는 그 특성상(노후화된 디스크, 물리적 충격, 펌웨어의 버그 등) 데이터가 부패할 수 있으며, 사용자는 이러한 문제를 인식하지 못하게 되는 경우가 있습니다. 이를 방지하기 위해서 minIO는 해쉬 알고리즘을 통해서 쓰고 읽을 때의 데이터가 동일한지 검증을 하고 있습니다.

Encryption

WORM

Identity Management

Continuous Replication

Global Federation

Multi-Cloud Gateway

minIO의 Architecture

minIO는 클라우드 친화적으로 설계되었기에 경량의 컨테이너로 구동이 가능합니다. 이러한 컨테이너는 40MB이하의 바이너리 파일입니다.

아키텍처 개념도

  • 위와 같이 여러 노드에 분산된 minIO를 하나로 묶어서 사용이 가능합니다.
  • 어플리케이션 수준에서는 S3 API를 이용하여 자원에 접근할 수 있습니다.
  • 클러스터 내부에 minIO간에는 RESTful API를 통해서 통신합니다.

  • minIO는 내부적으로 3개의 Layer로 구성되어 있습니다.
  • S3 Lyaer는 대외적인 네트워크를 처리하는 영역입니다.
  • Object Layer는 Cache, Compression, Encryption, Erasure code, Bitrot와 같은 앞서 언급된 기능들을 수행하는 영역입니다.
  • Storage Layer는 File System과 직접적으로 통신하는 영역입니다.

출처

책 <클린 아키텍처>의 [Part3 설계원칙]을 참조하여 정리한 내용입니다.

개요

SOLID원칙은 함수와 데이터 구조를 클래스로 배치하는 방법, 그리고 이들 클래스를 서로 결합하는 방법을 설명해준다.

나쁜 벽돌을 쌓아올려 건물을 짓는다면 당연히 좋은 아키텍처가 나올 수 없다. 그렇다고 좋은 벽돌을 이용한다고 무조건 좋은 아키텍처가 나오는 것도 아니다. SOLID원칙은 좋은 벽돌을 이용해서 좋은 아키텍처를 만들어내는 원칙을 정의한다. 
구체적인 정의를 보자면, SOLID원칙은 "함수와 데이터 구조를 클래스로 배치하는 방법, 그리고 이들 클래스를 서로 결합하는 방법을 설명"하는 원칙이며, 모듈 수준에서 변경 용이성, 빠른 가독성, 재사용성 확보를 목표로 한다. 

SOLID원칙은 각 원칙의 앞글자를 따서 만들어진 이름이며, 따라서 5가지의 원칙이 있다.

  • 단일 책임 원칙(SRP, Single Responsibility Principle)
  • 개방-폐쇄 원칙(OCP, Open-Closed Principle)
  • 리스코프 치환 원칙(LSP, Liskov Substitution Principle)
  • 인터페이스 분리 원칙(ISP, Interface Segregation Principle)
  • 의존성 역전 원칙(DIP, Dependency Inversion Principle)

단일 책임 원칙(SRP, Single Responsibility Principle)

하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다.

여기서 액터는 특정 변경을 요청하는 한 명 이상의 집단을 가리킨다. 
모듈은 간단히 말하면 '소스파일'이고, 언어에 따라서 적용되지 않는 경우에는 "함수와 데이터 구조로 구성된 응집된 집합"이다. 

단일 책임 원칙을 위반하는 사례와 이에 따른 해결책을 살펴보자.

잠재적 위험 - 공통 메소드의 변경

급여 어플리케이션에서 Employee클래스가 있다고 가정해 보자. 위의 Employee클래스는 3가지 메소드를 갖는데 각각은 다음과 같다.

  • calculatePay : 급여를 계산하는 메소드, 회계팀에서 기능을 정의하며, CFO보고를 위해 사용한다.
  • reportHours : 근무시간을 보고하는 메소드, 인사팀에서 기능을 정의하며, COO보고를 위해 사용한다.
  • save : Employee 관련 데이터를 저장하는 메소드, DBA가 기능을 정의하며, CTO보고를 위해 사용한다. 

하나의 클래스가 3명의 Actor를 책임지기 때문에, SRP에 위반된 경우이다.
그렇다면 SRP를 위반한 것이 구체적으로 어떤 위험을 초래할 수 있는 지 살펴보자.

다시 다음과 같은 사례를 가정해보자.
CFO에 보고되기 위해 사용되는 calculatePay메소드와 COO에 보고되기 위해 사용되는 reportHours메소드가 초과근무를 제외한 근무 시간을 계산하는 regularHours를 공통적으로 호출한다고 해보자.

이러한 상황에서 CFO의 요청으로 초과근무를 제외한 근무시간을 계산하는 메소드(regularHours)에 변경이 일어난다면, reportHours또한 영향을 받을 것이고, COO에게 보고되는 내용 또한 변경이 발생하게 된다. 이러한 변경이 인식되지 못한다면 큰 손실로 이루어질 위험이 있다.

잠재적 위험 - 병합시 충돌 상황

COO와 CTO 각각의 요청에 의해서 개발자 A와 B가 새로운 브랜치를 만들어서 변경을 수행한다고 가정해보자. 
CTO의 요청은 Employee클래스 데이터 구조의 변경이고 COO의 요청은 보고서 구조의 변경이다. 이 둘의 요청은 병합시 충돌을 야기할 것이다. 
이러한 충돌은 Employee클래스가 책임지고 있는 COO, CTO, CFO모두에게 잠재적인 위험이 될 수 있다.

해결책1 - 모듈의 분리

다음과 같이 하나의 모듈이 하나의 Actor를 책임진다면, 잠재적인 위험들을 모두 제거할 수 있다. 

  1. 공통적으로 사용하는 메소드의 변경으로 발생하는 위험이 제거 되고,
  2. 각각의 Actor에 의해서 발생하는 변경은 서로 다른 모듈의 변경만을 야기하므로 병합시 충돌 상황 또한 예방할 수 있다.

해결책2 - 퍼사드(Facade) 패턴

해결책1과 같이 단순히 모듈을 분리하면 개발자 관점에서 세 가지 클래스를 각각 추적해야 한다는 단점이 있다. 
이를 보완하는 것이 위와 같은 퍼사드 패턴이다. 
하나의 퍼사드 클래스에서 각 클래스들을 인스턴스화하고 행위들을 각각의 인스턴스에 위임 한다.

해결책3 - 중요 업무 규칙과 데이터의 근접 배치

어떤 개발자는 가장 중요한 업무 규칙을 데이터와 가깝게 배치하는 것을 선호한다. 그러한 경우에는 위와같이 클래스를 구성해 볼 수 있다.

결론

단일 책임 원칙은 클래스 수준의 원칙이다. 이보다 상위 수준(컴포넌트, 아키텍처)에서도 동일한 내용의 원칙이 다른 형태로 등장한다.

  • 컴포넌트 수준 -> 공통 폐쇄 원칙(Common Closure Principle)
  • 아키텍처 수준 -> 아키텍처 경계(Architectural Boundary)의 생성을 책임지는 변경의 축(Axis of Change)

 

요약해보자면..

  • 단일 책임 원칙은 하나의 모듈이 하나의 액터를 책임지도록 하는 클래스 수준의 설계 원칙이다.
  • 단일 책임 원칙을 위반할 경우, 공통 메소드 변경으로 인한 영향과 병합시 충돌상황과 같은 위험이 발생할 수 있다.
  • 단일 책임 원칙을 구현하는 방법으로,
    • 간단하게는 모듈을 분리하는 것이고
    • 개발적 관점을 고려한다면 퍼사드 패턴을 차용하는 것이다.
    • 선호에 따라서 중요한 비즈니스 로직과 데이터를 가깝게 배치하고, 퍼사드 패턴을 부분적으로 적용해 볼 수도 있다.

책 <클린 아키텍처>의 [Part2 벽돌부터 시작하기:프로그래밍 패러다임]을 참조하여 정리한 내용입니다.

람다(lambda)계산법을 비롯한 함수형 프로그래밍에 대한 기본적인 이해가 부족하여 본문의 요지만 간략히 정리합니다.

 

가변 변수


함수형 언어에서 변수는 변경되지 않는다.

대표적인 함수형 언어인 클로저와 자바의 극명한 차이는 가변 변수 사용 여부에 있다. 가변 변수는 프로그램 실행 중에 상태가 변할 수 있는 변수이며, 자바 언어는 가변 변수를 사용하는 반면 클로저는 가변 변수를 사용하지 않는다.

아키텍처를 고려할 때 변수의 가변성이 중요한 이유는 경합 조건(Race Condition), 교착상태(Deadlock), 동시성 문제(Concurrent Update)가 모두 가변 변수에 의해서 발생하기 때문이다. 

그러나 불변하는 변수만을 사용하여 프로그램을 만드는 것이 가능할지라도 자원이 한정되어 있다는 점에서 타협이 필요하다. 대표적인 타협안 중 하나는 컴포넌트의 분리다. 즉, 가변 컴포넌트와 불변 컴포넌트를 분리하여 프로그램을 설계하는 것이다.

 

가변성의 분리

프로그램을 구성하는 컴포넌트를 불변과 가변으로 분리할 경우, 하나의 불변 컴포넌트는 하나 이상의 가변 컴포넌트와 통신하도록 설계하며, 가변 컴포넌트는 트랜잭션 메모리와 같은 전략을 사용한다.

 

이벤트 소싱 전략

가변성을 분리하는 방법을 사용한다는 것은 저장공간과 연산 능력의 한계를 전제로 한다. 그런데 만약 이러한 한계가 없다면?  이벤트 소싱은 상태가 아닌 트랜잭션을 저장하는 전략으로서 충분한 저장공간과 연산 능력이 있다면 유효할 수 있는 전략이다

 

요약을 해보자면..

  1. 경합 조건, 교착 상태, 동시성 문제는 가변 변수에 의해서 발생하는 문제이다.
  2. 함수형 패러다임의 가장 큰 특징은 가변 변수를 사용하지 않는다는 것이다.
  3. 자원이 충분하다면 이벤트 소싱 전략을 이용하여 순수 함수형 프로그램을 만들 수 있다.
  4. 그렇지 않다면 컴포넌트를 가변과 불변으로 분리하여 적용해 볼 수 있다.

 

참고 문헌

  • 로버트C.마틴, 클린 아키텍처(소프트웨어 구조와 설계의 원칙), 인사이트, 송준이 옮김, 2019

책 <클린 아키텍처>의 [Part2 벽돌부터 시작하기:프로그래밍 패러다임]을 참조하여 정리한 내용입니다.

서론 - 객체지향을 설명하는 3가지 방식

좋은 아키텍처를 만드는 일은 객체지향 설계 원칙을 이해하고 응용하는데서 출발한다.


일반적으로 객체지향을 설명하는 방식은 3가지가 있다.

  1. 객체지향은 "데이터와 함수의 조합"이다.
  2. 객체지향은 "실제 세계를 모델링하는 새로운 방법"이다.
  3. 객체지향은 캡슐화, 상속, 다형성이라는 3가지 조건을 충족한 패러다임이다.

위와 같은 설명은 아키텍처의 관점에서 만큼은 충분하지 못하다.


"데이터와 함수의 조합"

객체지향이 "데이터와 함수의 조합"이라는 말은 object.function()과 function(object) 두 방식이 다르다는 것을 내포한다. 그러나 이는 사실상 같다.(저자는 다르다는 주장을 터무니 없다고 표현한다.) 함수에 데이터 구조를 전달하는 방식은 객체지향을 발명하기 이전부터 사용되어져왔다.


"실제 세계를 모델링하는 새로운 방법"

저자는 이러한 설명을 "얼버무리는 수준"의 대답이라고 표현한다. 저자는 "실제 세계를 모델링"한다는 것의 의미가 모호할 뿐더러, 그러한 방향을 추구해야 하는 이유에 대해서 의문을 던진다.

학교 다닐 때 특정 교수님이 이와 유사한 형태로 객체지향을 설명하셨었다. 설득력있다는 생각에 오랫동안 머리 속에 담아 두었던 생각인데.. 너무 쉽게 부정당한 느낌..


캡슐, 상속, 다형성

위 세가지 개념에 기대는 사람들은 이들을 조합하여 객체지향을 설명하거나, 이들을 객체지향의 기본조건으로 내세운다.

위 세가지 개념은 뒤에서 보다 자세히 다룬다.



캡슐화

객체지향언어는 접근 제어자(private, public, protected)와 같은 문법을 이용하여 효과적으로 데이터와 함수를 캡슐화하는 것을 지원한다. 그러나 C언어로도 캡슐화를 구현할 수 있으며, 이는 객체지향 언어보다 강력하다. 이 말인즉슨, 객체지향언어는 캡슐화를 강제하지 않으며 프로그래머가 올바르게 사용할 것이라는 믿음을 기반으로 설계되어졌고, 이러한 측면에서 객체지향언어들은 기존의 C언어보다 캡슐화가 완전하지 못하다.


이러한 이유로 캡슐화가 객체지향을 규정하는 조건이 되기에는 애매한 구석이 있다.



상속

상속 또한 C언어로 구현이 가능하다. 다만 객체지향 언어가 훨씬 편리한 상속 기능을 제공하는 것은 사실이다.


저자는 캡슐화에 대해선 점수를 줄 수 없고 상속에 대해선 0.5점 정도를 부여한다고 말한다ㅋㅋ..
실제 책에선 캡슐화, 상속 모두 C, C++, Java의 예제를 통해서 꽤나 상세하게 다룬다.



다형성

다형성은 함수를 가리키는 함수 포인터를 응용한 기법이며, 함수 포인터는 사실상 40년대 후반 폰노이만 아키텍처 이래로 사용된 방식이다. 물론 함수 포인터를 직접 다루는 것은 까다로운 작업이기 때문에 함수 포인터를 안전하고 편리하게 사용할 수 있게 하는 객체지향의 다형성이 강력한 건 사실이다. 그러나 주목해야 할 것은 다형성이 제어의 흐름(함수 포인터)을 간접적으로 전환한다는 사실이다.

다형성의 진가는 제어의 흐름을 간접적으로 제어한다는 점에 있다. 여기서 의존성의 역전이 일어난다.



의존성의 역전

다형성 이전 소프트웨어에서 제어의 흐름은 일반적으로 고수준 -> 중간 수준 -> 저수준 함수의 순서로 진행되었다. 이 말은 고수준의 함수가 저수준의 함수를 의존한다는 말이며, 저수준 함수에서 변경이 발생할 경우 중간 수준과 고수준에서도 동시에 변경이 일어난다는 말이 된다. 반면에 객체지향 언어의 경우 저수준 함수에 대해서 인터페이스를 통해 규약을 강제할 수 있다. 즉, 고수준 함수가 저수준 함수를 의존하지 않게 된다. 반대로 저수준 함수가 고수준의 인터페이스를 의존하게 된다. 결론적으로 객체지향 언어를 이용할 경우 의존성의 방향을 마음대로 변경할 수 있다.

글로만 설명하기엔 다소 추상적인 개념들이 많다. 책에서는 그림과 코드로 보다 상세하게 설명한다.
저자는 이에 덧붙여 "플러그인 아키텍처", "배포 독립성", "개발 독립성"들을 언급하며 의존성이 역전된 객체지향언어의 장점을 강조한다.



요약(을 해보자면..)

  1. 객체지향을 설명하는 일반적인 세 가지 방식은 불충분하다. ("데이터와 함수의 조합", "실제 세계를 모델링하는 새로운 방법", 캡슐화-상속-다형성)
  2. 왜냐하면 객체지향의 진가는 다형성 그 자체가 아니라 다형성과 같은 기능을 통해서 일어나는 의존성의 역전이기 때문이다.
  3. 의존성의 방향을 제어하면 "플러그인 아키텍처"를 구현할 수 있으며, 이에 따라 "배포 독립성"과 "개발 독립성"이라는 장점을 취할 수 있다.



자의적인 해석이 많이 섞여 있을 수 있으니, 꼭 책을 참고하시길 바랍니다.

책 <클린 아키텍처>의 [Part2 벽돌부터 시작하기:프로그래밍 패러다임]을 참조하여 정리한 내용입니다.


서문

  • 1945년 경 앨런튜링은 바이너리 언어를 사용하여 반복문, 분기문, 할당문, 서브루틴, 스택 등과 같은 구조를 사용하여 프로그램을 만들었다.
  • 1940년대 후반 어셈블리 언어, 1951년 A0컴파일러를 필두로 수 많은 언어들이 탄생하였다.
  • 이처럼 언어적인 차원에서 수 많은 혁신이 일어난 것처럼, 프로그래밍 패러다임에 있어서도 많은 변화가 이루어져왔다.
  • 패러다임은 "프로그래밍을 하는 방법"을 정의하며 언제 어떠한 구조를 사용해야 할 지에 대한 기준이 된다.

구조적 패러다임

데이크스트라는 무부별한 점프(goto)는 프로그램 구조에 해롭다는 사실을 제시했다.

 

C언어에는 goto문이 있다는데, C언어를 안 써본 나는 오히려 goto문이 생소하다.
그렇다.. 구조적 패러다임은 goto문장과 관련이 있다.

 

구조적 패러다임은 무엇인가

구조적 패러다임은 if/then/else(분기문)와 do/while/until(반복문)과 같은 문법으로 프로그래밍을 하는 방식이다. 다시 말하면 goto문을 제약하고 분기와 반복문을 사용하는 기법이다.

 

구조적 패러다임은 현 시대의 프로그래밍 언어를 사용한다면 너무 당연하고 익순한 내용이다. 당연한 걸 당연하다고 말하는 이 패러다임..
'등장'의 이유가 중요하다.

 

구조적 패러다임의 등장 배경

데이크스트라라는 아저씨, 아니, 컴퓨터 과학자가 등장한다.
1950년 대, 데이크스트라가 살던 시절에는 바이너리 언어로 프로그래밍을 하던 시절이였고, 당시에는 간단한 프로그램일지라도 많은 세부사항들을 조작해야만 했다. 그래서 사소한 세부사항을 놓쳐서 프로그램이 실패하는 상황이 자주 벌어졌다.
이러한 상황에서 데이크스트라는 프로그램의 올바름을 입증할 필요성을 느꼈고, 수학의 공리, 정리, 따름정리, 보조정리와 같은 개념을 차용하고자 한다. 즉 작은 단위의 입증된 코드들을 결합하여 전체 코드의 올바름을 입증하고자 하였다.
그런데 무분별한 goto문은 전체 프로그램을 작은 단위로 분해하는데 방해가 된 반면에 특정 방식으로 goto문을 사용하는 것은 오히려 도움이 되었다. 이러한 방식이 바로 분기문(if/then/else)과 반복문(do/while/until)이다.

 

자세한 증명 방식은 생략, 그러나 엄밀한 의미에서 증명은 아니다

 

그러나 증명은 없었다

사실상 프로그램에서 세세한 기능들을 엄밀하게 증명하는 것은 고된 작업이었고, 사실상 그러한 입증을 할 수는 없었다. 그러나 다이크스트라는 과학적 의미에서의 입증은 가능하다고 생각하였다. 즉 어떤 서술(여기선 프로그램)에 대해서 반례가 나오지 않았다면 그것은 목표에 부합할 만큼은 참이라고 여길 수 있다.

이런 관점에서 테스트를 얘기 하자면..

그런 의미에서 테스트는 해당 프로그램이 거짓임을 입증하려는 시도라고 볼 수 있다. 그러므로 테스트 코드가 통과했다면, 해당 프로그램은 목표한 기능에 대해서 만큼은 참이라고 말할 수 있다. 다시 말해 프로그램이 목표한 기능에 대해서 올바르게 동작할 것을 입증하였다고 볼 수 있다.

 

대신에 과학적 증명이 있었다.

 

요약(을 해보자면..)

  1. goto문이 난무하던 옛날(50-60년대)에도 프로그램의 실패가 잦았음.
  2. 다이크스트라라는 컴퓨터 과학자는 프로그램의 정상동작을 보장 할 방법으로 수학적 증명의 방법을 차용하였지만, 결국엔 과학적 증명방식을 창안함.
  3. goto문을 특정 방식으로 제약하면 프로그램의 (과학적)증명이 수월하였음.
  4. 그러한 제약 방식들이 분기문(if/then/else)과 반복문(do/while/until)임.

 

 

참고 문헌

  • 로버트C.마틴, 클린 아키텍처(소프트웨어 구조와 설계의 원칙), 인사이트, 송준이 옮김

들어가며

#1 이전글을 작성하기 위해 자료를 찾던 중 JMS(Java Message Service)를 소개하는 오라클 문서를 발견했다.
해당 문서에서는 MOM(Message-Oriented Middleware)이라는 용어가 나왔는데,
당시 다소 낯설은 용어 때문에 자세히 읽어보는 것을 보류했었다. 그래서 이번에 MOM이 무엇인지 살펴보려 한다.




미들웨어(Middleware)

비즈니스 상황에 따라 지속적으로 변하는 소프트웨어의 변화를 수용하기 위해서는 가능한 효과적으로 새로운 컴포넌트를 통합하고 기존의 컴포넌트들을 확장시켜야 한다. 다양한 컴포넌트들을 통합하는 가장 쉬운 방법은 서로 다른 형태임에도 불구하고 커뮤니케이션이 가능하도록 계층(layer)을 제공하는 것이다. 이러한 계층을 미들웨어라고 한다.

미들 웨어는 크게 통신 방식에 따라 3종류로 분류되며 다음과 같다.

  1. RPC-based Middleware(Remote Procedure Call)
  • 분리된 어플리케이션의 프로시저(procedure)호출을 가능하게 하는 미들웨어
  1. ORB-based Middleware(Object Request Broker)
  • 객체(Object)의 분산 및 공유를 가능하게 하는 미들웨어
  1. MOM-based Middleware(Message Oriented Middleware)
  • 메시지(Message)송,수신을 통하여 분산된 어플리케이션 간의 통신을 가능하게 하는 미들웨어

위와 같은 분류체계는 프로시져(Procedure), 객체(Object), 메시지(Message)중 어떤 것을 중심으로 통신하는지에 따라 분류한 것으로 보여진다만..
프로시져, 객체, 메시지의 개념적인 구분에 대한 설명이 자세히 나와있지는 않은 듯 하다.




MOM(Message-Oriented Middleware)

MOM시스템은 기본적으로 클라이언트, 메시지, MOM제공자(provider)로 구성된다.
MOM제공자는 메시지를 전달(deliver) 및 전송(route)함에 있어서 크게 2가지 아키텍처를 갖는다.(일부 MOM제품들은 2가지 형태의 방법을 모두 제공하기도 한다.)

  1. 중앙집중화된 서버 형태
  2. 분산된 서버 형태

MOM시스템의 장점

RPC, ORB기반의 시스템들이 동기적으로 통신하는 것과는 달리 MOM기반의 시스템은 비동기적으로 통신하며,
컴포넌트들 간에 상대적으로 약한 결합이 발생한다는 점에서 이점이 있다.
따라서 클라이언트는 메시지를 보내고 난 이후에 다른 작업을 진행할 수 있다.(응답을 기다리지 않고)

MOM시스템의 또 다른 이점은 관리자 인터페이스를 추가한다면 성능을 관측하고 조정할 수 있다는 것이다.
클라이언트 프로그램의 관점에서 보면 메시지 송수신과 메시지의 처리에만 집중하면 된다는 점에서 효과적이다.


MOM시스템의 단점

MOM시스템은 컴포넌트들 간에 느슨한 결합(비동기적인 통신) 때문에 Message처리가 실패하더라도 Message를 호출한 Client는 작업을 이어나간다.
즉, 이러한 점에서 실패를 추적하는 것이 까다로울 수 있다.




마치며

'미들웨어'도 'MOM'도 아직은 낯설지만 생각보다 어려운 내용이 아닌 것 같은 느낌을 받는다.
Celery와 Message Queue가 곧 Client와 Message Provider일 것이기에,
구현체로서의 Celery를 다루다보면 MOM에 더 익숙해질 수 있을 것 같다.
별개로, 영문 문서를 읽다보니 뒤로 갈수록 빨리 마무리 짓고 싶은 마음이 강해지더라..

Celery란 무엇인가

Celery공식 문서에서는 Celery를 이렇게 소개한다.

Celery is a simple, flexible, and reliable distributed system to process vast amounts of messages, while providing operations with the tools required to maintain such a system

정의에 따르자면 Celery는 (단순하고 유연하고 신뢰할만한) '메시지를 처리하는 분산된 시스템'이다.

다만 해석을 더하자면, Celery는 시스템 그 자체라고 보기는 어렵고 시스템을 구성하는 한 요소로 봐야될 듯 하다.
위 정의에서도 언급되듯이 Celery는 시스템을 유지하기 위해서 다른 도구들을 필요로 한다.
추측하자면 도구들은 Redis 혹은 RabbitMQ를 비롯한 Message Broker를 지칭하는 듯 하다.


메시징 시스템

그렇다면 메시지를 처리하는 분산된 시스템은 무엇일까.. 무슨 일을 하는, 어떤 목적을 갖는 시스템인 것일까..
보다 익숙한 표현으로서 '메시징 시스템'을 검색하면 JMS(Java Message Service)를 소개하는 오라클 문서가 나온다.
해당 문서에서는 MOM(Message-Oriented Middleware)라는 단어가 나온다.. 익숙치 않은 '미들 웨어'라는 단어가 다시 등장한다.
MOM에 대한 이해는 접어두고 위키피디아를 이용해보자..

위키피디아에서 Messaging System이 나온다. 정확히는 Enterprise Messaging System이다. 정의를 살펴보자.

An enterprise messaging system (EMS) or messaging system in brief[1] is a set of published enterprise-wide standards that allows organizations to send semantically precise messages between computer systems.

'의미론적으로 정확한(sementically precise)메시지 전송을 컴퓨터 시스템 간에 가능하게하는 표준들의 집합'이라고 한다.
다소 명확하게 이해가 안되는 부분이 있지만 억지로 해석을 해보자면

  • '의미론적으로 정확한(sementically precise) 메시지' -> 아마 통신 규약(protocol)을 염두한 것이 아닐까 추측해본다.
  • '컴퓨터 시스템 간에', '메시지 전송' -> 메시징 시스템이 분산된 시스템 사이에서 메시지를 전송하는 시스템인건 맞는 듯하다. 컴퓨터가 전송하는게 뭐.. 데이터겠지만, 다만 메시지가 정확히 어떤 형태인지는 아직 아리송하다.
  • '표준들의 집합' -> 표준이라는 건 규칙을 말하는 것일까, 가장 이해하기 어려운 부분이다.

정의에 너무 천착할 수는 없고 여하튼 그 이름에서 느껴지는 이미지를 크게 벗어나지는 않는다.

'분산된 시스템 사이에서 메시지 형태의 데이터 송수신을 가능하게 하는 시스템'정도로 정리해 볼 수 있지 않을까 싶다.


마무리

목표는 Celery의 정확한 역할과 사용법을 찾아 보고, 나아가 Message Broker로서 자주 사용되는 Redis와 RabbitMQ의 차이를 정리해보는 것이였지만.. 역시 문서로 정리한다는 것은 품이 많이 들어간다.
MOM은 꼭 다시 살펴보자.

'IT기술 관련 > Python' 카테고리의 다른 글

[Celery] #2 MOM(Message Oriented Middleware)  (0) 2020.08.23

+ Recent posts