Discover us

About us

Projects

Blog

Events

Members

Blog

GDSC CAU 개발자와 디자이너의 작업 과정과
결과물을 공유하는 공간입니다.

어떻게 프로젝트를 시작하게 되었고,
진행하면서 느낀 개발자와 디자이너의
생생한 스토리를 직접 확인해보세요!

Development

Mediator/Middleware 패턴

  • #Front-End
  • Yujin Son
  • 2024. 10. 31.

Mediator/Middleware 패턴

Mediator/Middleware 패턴

 

1. 사용이유

이 패턴은 공항에서 비행기(object)의 동선을 관리하는 관제소(mediator)에 비교할 수 있습니다.
비행기끼리 직접 통신하면 사고로 이어질 수 있지만, 관제소에서 상황을 전달받아 통제를 하게 되면 서로 충돌 없이 안전하게 활주로를 이용할 수 있게 됩니다.
notion image
→ 객체끼리 서로 통신하게 하여 [ 다 : 다 ] 의 관계를 이루는 모습
 
마찬가지로, 자바스크립트에서도 마찬가지입니다. 종종 여러 객체들이 서로 데이터를 주고 받는 상황이 생기곤 하는데, 컴포넌트가 많아질수록 통신의 횟수는 많아지고 점점 흐름을 파악하기 어려워 질 것입니다.
또한, object간의 통신은 유지보수가 쉽고 다른 object를 건드리지 않으면서, 애플리케이션의 일부분을 안전하게 수정할 수 있는 방식으로 이루어져야 합니다.
 
따라서 아래와 같이 컴포넌트들이 서로 직접 통신하는 대신 중재자 역할을 하는 객체를 통하도록 하는 것입니다.
notion image
→ 객체의 요청들을 모두 중재자 객체에게 보내는 모습
중재자 객체가 요청을 받아 이를 필요로 하는 객체들에게 전달을 하는 것입니다. 주로 중재자는 객체나 함수로 구현됩니다.
 

2. 실무에서 사용되는 예) 채팅 구현

실무에서 이 중재자 패턴이 적합한 곳은 채팅을 구현할 때입니다.
채팅 앱에서 사용자들은 직접 서로 메시지를 주고 받지 않고, 채팅 서버에 메시지를 전송하고 서버가 각 사용자에게 메시지를 전달하는 형태이기 때문입니다.
// 중재자 역할 : 메시지를 관리하는 역할 class ChatRoom { // 사용자가 보낸 메시지를 기록하고 콘솔에 출력 logMessage(user, message) { const time = new Date() const sender = user.getName() console.log(`${time} [${sender}]: ${message}`) } } //ChatRoom의 사용자 객체 class User { constructor(name, chatroom) { this.name = name this.chatroom = chatroom } getName() { return this.name } send(message) { this.chatroom.logMessage(this, message) } }
  • 사용자는 ChatRoom과 연결되는 User를 만들어낼 수 있고, 각 인스턴스는 send 메서드를 통해 다른 사용자에게 메시지를 전송할 수 있습니다.
  • 사용자가 ChatRoom에 직접 접근하지 않고, 자신의 메시지를 ChatRoom에 전달하기 때문에, 사용자 간의 직접적인 의존성을 줄이고 메시지 전달 로직을 중앙 집중화할 수 있는 것입니다.
 

<MiddleWare>

  • MiddleWare는 Middle software의 약자로, request와 respond 사이에 있는 controlelr입니다.

3. 사례 분석

  • 많이 사용하는 웹 서버 프레임워크인 Express.js 를 예시로 들어보겠습니다.
    • express가 middleware라는 용어를 대중화하여 구체적으로 디자인 패턴을 구현했는데요, 이를 통해 개발자가 프레임워크의 핵심에 손대지 않고도 새로운 기능을 쉽게 만들고 배포하여 기능을 추가할 수 있도록 한 것입니다.
    • 특정 라우팅 경로에 대해 콜백을 추가함으로써 요청을 처리할 수 있습니다.
  • / 경로를 요청했을 때 요청에 헤더를 추가해야 한다고 가정해봅시다.
    • 아래의 예시와 같이 미들웨어를 추가하여 처리할 수 있습니다.
      • const app = require('express')() app.use('/', (req, res, next) => { req.headers['test-header'] = 1234 next() })
        - next함수는 요청-응답 사이클에 걸려있는 다음 콜백을 호출합니다.
        notion image
      • middleware에 처리를 위한 새로운 데이터가 수신되면 등록된 middleware 들이 비동기 순차 실행 흐름으로 호출되는데, next함수가 req - res 사이클에 걸려있는 다음 callback을 호출하는 것입니다.
      • 각 middleware는 데이터의 추가적인 처리를 중단시킬 수 있습니다. callback을 호출하지 않거나 오류를 전파하여 동작합니다.
      • 아래 예제에서는 위의 예시에서 헤더가 잘 추가되었는지 검사하는 미들웨어를 추가한 것입니다.
        • 이전 콜백의 변경 사항을 다음 콜백에서 확인할 수 있는 것입니다
        const app = require('express')() app.use( 1. '/', (req, res, next) => { 2. req.headers['test-header'] = 1234 3. next() }, 4. (req, res, next) => { 5. console.log(`Request has test header: ${!!req.headers['test-header']}`) 6. next() } )
        1. 클라이언트가 / 경로에 요청을 보냅니다.
        1. 첫 번째 미들에워가 실행되어 요청 객체의 헤더에 ‘test-header’를 추가 한뒤 값을 설정해줍니다.
        1. 그런 다음, next()를 호출해서 다음 미들웨어로 이동합니다.
        1. 그렇게 두번째 미들웨어가 실행됩니다.
        1. 두번째 미들웨어는 요청 객체의 헤더에 ‘test-header’가 추가되었는지 확인하고, 그 결과를 로그에 출력하는 것입니다.
        1. 마지막으로 다시 next()를 호출하여 요청-응답 사이클의 다음 단계로 넘어가는 것입니다.
 
  • 정리하자면, 이 미들웨어 패턴은 여러 객체 간 다대 다의 통신을 하나의 관리 포인트를 통하도록 만들어 관계를 단순하게 만들어주는 패턴입니다.
  • 그러나, 중재자에 권한이 집중되어 있기 때문에, 잘못된 중재자의 설계는 더 복잡한 객체를 생성할 수 있다는 단점이 있습니다.