본문 바로가기
Javascript/Node.js

Middleware Pattern - 행위 디자인 패턴

by v8rus 2022. 2. 14.

미들웨어 (Middleware)

Node.js 의 가장 독특한 패턴
일반적 미들웨어 : 하위 수준 메커니즘 추상화에 도움되는 소프트웨어 제품군
여기서는 "하위 계층의 서비스들과 응용 프로그램 사이에서 접착제 역할을 한다"라고 보면되겠음

 

 

1. Express의 미들웨어

Express 미들웨어는 파이프라인으로 구서오디어 들어오는 HTTP 요청과 요청에 대한 응답을 처리하는 서비스 집합임
Express 는 자유롭고 작은 웹 프레임워크, 개발자가 핵심에 손 대지 않고도 쉽게 기능을 쉽게 만들고 배포 가능
Express 미들웨어 특징

function (req, res, next) { ... }

req, res는 이미 알고있고, next : 다음 미들웨어를 트리거 할 때 호출되는 콜백

어플에 대한 지원을 통해 요청을 처리하는 핸들러가 주요 비즈니스 로직에만 집중할 수 있도록 하는 액세서리들임

 

 

2. 패턴으로서의 미들웨어

Express 미들웨어 구현에 사용되는 기술 : 인터셉터 필터 (Intercepting Filter), 책임 사슬 (Chain of Responsibility) 패턴
보다 일반적으로 스트림을 연상시키는 파이프라인 처리로 보임
미들웨어는 함수 형태의 처리장치, 필터 및 핸들러들의 집합이 모든 종류의 데이터 전처리 및 후처리를 수행하기 위해 비동기적으로 연결되어 형성된 특정 패턴을 말함
장점 : 유연성, 매우 적은 노력으로 플러그인 인프라를 얻을 수 있음, 시스템 확장에 간결한 방법 제공

======== 미들웨어 이미지? 들어가면 될듯 ===============
미들웨어 패턴의 필수적 컴포넌트 : 미들웨어 관리자 Middleware Manager
-새로운 미들웨어는 use()를 호출하여 등록, 일반적으로 미들웨어는 파이프라인 끝에만 추가 할 수있지만 규칙은 아님
-데이터 입력 시 등록된 미들웨어들이 비동기 순차 실행 흐름으로 호출됨. 각 유닛은 이전 유닛의 실행결과를 입력 받음
-미들웨어는 처리 중단이 가능, 함수 호출, 콜백 중단 ,오류 전파 등등

전파 방식에 엄격한 규칙은 없음, 데이터 수정사항 전파하기 위한 전략은?
-추가속성 및 함수를 사용해 입력된 데이터를 증강함
-데이터 불변성을 지키면서 처리하고 항상 새로운 복사본으로 처리결과를 반환

 

 

3. ZeroMQ 를 위한 미들웨어 프레임워크 만들기

ZeroMQ 메시징 라이브러리로 패턴 시연
다양한 프로토콜을 사용해 네트워크에서 원자 메시지 교환을 위한 간단한 인터페이스 제공
성능 준수, 커스텀 메시징 아키텍처 구현을 용이하게 하기 위해 특별하게 만듦 => 복잡한 분산 시스템 구축에 사용

ZeroMQ 의 인터페이스는 매우 낮은 수준에서 메시지에 문자열에 바이너리 버퍼만 사용 할 수있음
데이터의 인코딩이나 사용자 정의 데이터형식은 라이브러리 사용자가 구현해야 함

Ex) ZeroMQ 소켓을 통해 전달되는 데이터의 전처리 및 후처리 추상화 미들웨어 만들기 ,JSON 객체로 투명하게 작업 가능, 압축 도 할거임

 

3-1. 미들웨어 관리자

첫번째 단계는 새로운 메시지를 받거나 보낼때 미들웨어 파이프라인을 실행하는 컴포넌트를 만드는 것

// zmqMiddlewareManager.js
export class ZmqMiddlewareManager {
  constructor (socket) {
    this.socket = socket;
    this.inboundMiddleware = [];
    this.outboundMiddleware = [];
    this.handleIncomingMessages()      // 소켓에서 오는 메시지 즉시 처리 시작함
      .catch(err => console.error(err));
  }

  async handleIncomingMessages () {
    for await (const [message] of this.socket) {     // 비동기ㅗㄹ 처리, 처리 후 inboung 미들웨어 목록에 전달
      await this
        .executeMiddleware(this.inboundMiddleware, message)
        .catch(err => {
          console.error(`Error while processing the messages`, err);
        })
    }
  }

  async send (msg) {
    const finalMessage = await this
      .executeMiddleware(this.outboundMiddleware, msg);   // 수신된 인자를 outboundMW 파이프라인으로 인자를 전달

    return this.socket.send(finalMessage);
  }

  use (middleware) {    // 미들웨어 추가 시 사용, 상호보완적으로 함수가 역순으로 실행되어야 함 (JSON 직렬화 역직렬화 순서때문에 그럼)
    if (middleware.inbound) {
      this.inboundMiddleware.push(middleware.inbound);      // 마지막 요소에 추가
    }
    if (middleware.outbound) {
      this.outboundMiddleware.unshift(middleware.outbound);       // unshift 가 시작부분에 삽입
    }
  }
  
  async executeMiddleware (middlewares, initialMessage) {     // 미들웨어 기능을 실행하는 부분, 입력받은 미들웨어 배열의 각 함수는 차례로 실행됨, 실행 결과는 다음 함수로 ㅓㄴ달됨
    let message = initialMessage;
    for await (const middlewareFunc of middlewares) {
      message = await middlewareFunc.call(this, message);       // call 에 대해서 알아둬야 할듯, bind apply 등등
    }

    return message;
  }
}

! 간결함을 위해 오류 처리 MW 파이프라인은 지원 안했음. 일반적으로 미들웨어 함수가 오류 전파 시 오류 전용 미들웨어 함수 집합이 실행 됨

 

3-2. 메시지 처리를 위한 미들웨어 구현

JSON 메시지 직렬화 및 역직렬화 하는 필터가 목표임

// jsonMiddleware.js
export const jsonMiddleware = function() {
  return {
    inbound (msg) {   // 수신된 메시지를 역직렬화
      return JSON.parse(msg.toString());
    },
    outbound (msg) {  // 데이터를 문자열로 직렬화 한 다음 버퍼로 반환
      return Buffer.from(JSON.stringify(msg));
    },
  }
}

// 비슷한 방식으로 zlib 코어 모듈로 메시지를 압축/해제 하기위한 zlibMiddleware.js에 한쌍의 미들웨어 함수 구현 가능
import {inflatRaw, deflatRaw} from 'zlib';
import {promisify} from 'util';

const inflatRawAsync = promisify(inflatRaw);
const defaltRawAsync = promisify(deflatRaw); 

export cosnt zlibMiddleware = function () {
  return {
    inbound (msg) {
      retur ninflatRawAsync(Buffer.from(msg));
    },
    outbound(msg) {
      return defaltRawAsync(msg);
    }
  }
}

 zlib 미들웨어는 비동기적이므로 프라미스를 반환함

 

3-3. ZeroMQ 미들웨어 프레임워크 사용

위에서 만든 미들웨어 인프라 사용 가능 => 사용하기 위해 ZmqMiddlewareManager로 소켓들을 감쌀거임
클라이언트 - ping 보냄, 서버 - 수신된 메시지 되돌려주기

// server.js
import zeromq from 'zeromq';
import { ZmqMiddlewareManager } from './zmqMiddlewareManager.js';
import {jsonMiddleware} from'./jsonMiddleware.js';
import {zlibMiddleware} from './zlibMiddleware.js';

async function main() {
  const socket = new zeromq.Reply();
  await socket.bind('tcp://127.0.0.1:5000');    // 5000 포트에 바인딩

  const zmqm = new ZmqMiddlewareManager(socket);    // ZeroMQ 를 미들웨어 관리자로 감싼다음 미들웨어 추가
  zmqm.use(jsonMiddleware);
  zmqm.use(zlibMiddleware);
  zmqm.use({                                // 요청 처리 준비 완료, 요청 핸들러로 사용하는 다른 미들웨어 추가해서 수행
    async inbound (msg) {
      console.log('Received', msg);
      if (msg.action ==='ping') {
        await this.send({ action : 'pong', echo : msg.echo });
      }

      return msg;
    }
  });
  console.log('Server started');
}
main();

 

// client.js
import zeromq from 'zeromq';
import {ZmqMiddlewareManager} from './zmqMiddlewareManager.js';
import {jsonMiddleware} from './jsonMiddleware.js';
import {zlibMiddleware} from './zlibMiddleware.js';

async function main() {
  const socket = new zeromq.Request();    // 원격 호스트에 연결함
  await socket.connect(`tcp://127.0.0.1:5000`);

  const zmqm = new ZmqMiddlewareManager(socket);
  zmqm.use(zlibMiddleware());
  zmqm.use(jsonMiddleware());
  zmqm.use({
    inbound(msg) {
      console.log('Echoed back', msg);    // 최종적으로 ping 에 대한 응답을 출력함
      
      return msg;
    }
  })

  setInterval(() => {
    zmqm.send({action :'ping', echo: Date.now() })
      .catch(err => console.error(err));
  }, 100);

  console.log('Client connected');
}
main();

이로써 메시지를 투명하게 압축/해제 하고 직렬화/역직렬화 할 수있어서 핸들러가 비즈니스 로직에 집중 할 수있게 됨

 

 

4. Summary

미들웨어 패턴을 대중화한 라이브러리 : Express
-Koa, Express 후속제품, 동일한 팀이 만들고 철학과 주요 디자인 원칙을 공유함, 콜백 대신 async /await 같은 최신 프로그래밍 기술을 사용해 Express 의 미들웨어랑은 조금다름
-Middy, AWS Lambda 함수들을 위한 미들웨어 엔진임

댓글