2026년 7월 4일 토요일

[Code] 모듈러 모놀리딕(Modular Monolithic) + 프로세스 분리 & Python 예제 #2

모듈러 모놀리딕(Modular Monolithic) + 프로세스 분리 & Python 예제 #2

모듈러 모놀리딕으로 개발하다가, 특정모듈을 분리하려고 합니다. 여기서 분리는 프로세스(실행파일)을 독립적으로 분리하려고 합니다. 분리하려는 이유는 여러가지를 가정할 수 있을 것 같습니다. 

초기 개발할 때는 모듈러 모놀리딕으로 진행하다가 MacroService 또는 Citadel 아키텍처로 변환이 되는 시작점이 되는 것으로 보입니다. 개발 초기부터 MSA로 시작할 필요가 없다고 생각하고, 또한 MSA가 최종 목표도 아닙니다. 

아키텍처 전환 시, 발생하는 변화가 있습니다. (쉽게 표현하면, 프로세스가 나누어지면)
통신방식의 변환이 필요한 것입니다. In-Memory -> IPC/Network로 변경해야 하고, 코드가 더 들어갑니다. 
다시 말해서, 노출된 함수를 직접 호출하는 방식이 아닌, IPC 또는 Network로 통신을 해야 합니다. 


main_app            # [프로세스 1]
 - main.py
 - cli.py
 - feature_a.py
 - feature_b.py   # 로직수행하지 않음. 프로세스2로 요청을 보내는 대리인 역할

service_b            # [프로세스 2]
 - service_b.py    # 자체 웹 서버를 탑재한 B 기능 실행 파일



1. [프로세스 1] main.py | cli.py | feature_a.py (변경없음)


2. [프로세스 1] feature_b.py 
기존 비즈니스 로직 코드는 삭제되고, 독립된 B 프로세스 웹 서버로  HTTP POST 요청을 보내는 코드로 대체합니다. 

# main_app/feature_b.py
import requests

# 독립 실행된 B 프로세스의 REST API 주소
B_SERVICE_URL = "http://127.0.0.1:8000/execute"

def run(input_value: str) -> str:
    print(" [네트워크] 독립된 B 프로세스로 REST API 요청 전송 중...")
    
    # REST API에 보낼 JSON 데이터 구성
    payload = {"text": input_value}
    
    try:
        # B 프로세스 서버로 POST 요청 발송 (타임아웃 3초 설정)
        response = requests.post(B_SERVICE_URL, json=payload, timeout=3)
        
        if response.status_code == 200:
            data = response.json()
            return data.get("result", "응답 데이터 누락")
        else:
            return f" B 서비스 오류 (HTTP {response.status_code})"
            
    except requests.exceptions.ConnectionError:
        # B 프로세스가 죽어있어도 메인 시스템은 크래시 나지 않고 결함이 격리됨
        return "B 서비스 프로세스에 연결할 수 없습니다. (서버가 꺼져있음)"


3. [프로세스 2] service_b.py (분리된 독립 B 서비스 코드)
프로세스1과 완전히 별개로 실행되는 가벼운 웹 서버입니다. 기존 B기능의 비지니스 로직이 이관된 것입니다. (사전에 pip install fastapi uvicorn 설치 필요) FastAPI 사용

# service_b/service_b.py
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

app = FastAPI(title="독립된 B 기능 매크로서비스")

# 요청 데이터 구조 정의 (DTO)
class RequestData(BaseModel):
    text: str

def _pure_business_logic(data: str) -> str:
    """기존 b.py에 있던 핵심 비즈니스 로직"""
    return data[::-1]

@app.post("/execute")
def handle_b_feature(data: RequestData):
    """메인 프로세스로부터 REST API 요청을 받는 엔드포인트"""
    print(f"[B 프로세스 수신] 요청 데이터: '{data.text}'")
    
    # 비즈니스 로직 수행
    processed_text = _pure_business_logic(data.text)
    
    # JSON 형태로 응답 반환
    return {
        "status": "success",
        "result": f"[B 원격 프로세스 완료] 뒤집힌 결과 -> {processed_text}"
    }

if __name__ == "__main__":
    print("B 기능 독립 프로세스 서버를 시작합니다 (Port: 8000)...")
    # 8000번 포트에서 독립된 웹 서버 프로세스로 가동
    uvicorn.run(app, host="127.0.0.1", port=8000)



2개 실행파일을 모두 실행해야 합니다. 2개의 프로세스이므로 터미널에 두개 열어서 각각 실행합니다. 
 - python service_b.py
 - python main.py


추가 고려사항
 - 프로세스간에 통신하는 방식은 여러가지가 있지만, 여기서는 REST를 사용한 것입니다. 다른 방식으로 선정했다면, 그에 맞는 코드로 적용하면 됩니다. 

 - B기능이 자원(CPU, Memory)를 많이 소모하는 경우, B 실행파일만 별도의 고성능 서버에 실행할 수 있습니다. 코드에서 IP, Port만 변경해주면 됩니다. 

 - B기능이 자주 변경되어 배포해야하는 것은 메인 프로세스를 중단하지 않고 B 모듈/서비스만 배포하여 실행할 수 있습니다. (반대의 경우도 동일함)

 - B기능에 문제가 발생하면(메모리 누수로 인한 비정상 종료), 메인 프로세스에 영향이 거의 없습니다. 




Step #1 : 모든 코드가 하나의 큰 프로세스 내부에 있으며, In-Memory 함수 호출로 통신함
Step #2 : 메인 프로세스는 핵심기능(A)를 유지하고, 분리된 독립 B 서비스가 독립적으로 존재. 메인 프로세스 내부의 Feature_b.py는 로직수행이 아닌 REST 클라이언트가 되어 원격으로 독립 B 서비스를 REST API요청을 보냅니다. 


댓글 없음:

댓글 쓰기