Observer 패턴과 Publisher/Subscriber(Pub-Sub) 패턴의 차이점

|

본 글은 토비의 봄 TV 5회 스프링 리액티브 프로그래밍 (1) - Reactive Streams 영상을 보던 중 “Observer패턴과 Pub-Sub패턴의 차이”에 대한 얘기가 나와 궁금해 찾아 본 자료를 정리한 문서입니다.
“Head First Design Patterns” 책에는 Obaserver Pattern == Pub-Sub Pattern으로 나와 있지만 실제 찾아보면 비슷한 개념 사이에 확연한 차이점이 존재합니다.

가장 큰 차이점은 중간에 Message Broker 또는 Event Bus가 존재하는지 여부입니다.

Pattern Notification

Observer패턴은 Observer와 Subject가 서로를 인지하지만 Pub-Sub패턴의 경우 서로를 전혀 몰라도 상관없습니다.

Observer패턴의 경우 Subject에 Observer를 등록하고 Subject가 직접 Observer에 직접 알려주어야 합니다.

Pub-Sub패턴의 경우 Publisher가 Subscriber의 위치나 존재를 알 필요없이 Message Queue와 같은 Broker역활을 하는 중간지점에 메시지를 던져 놓기만 하면 됩니다.
반대로 Subscriber 역시 Publisher의 위치나 존재를 알 필요없이 Broker에 할당된 작업만 모니터링하다 할당 받아 작업하면 되기 때문에 Publisher와 Subscriber가 서로 알 필요가 없습니다.

Observer패턴에 비해 Pub-Sub패턴이 더 결합도가 낮습니다.(Loose Coupling)##

Publisher와 Subscriber가 서로의 존재를 알 필요가 없기 때문에 당연히 소스코드 역시 겹치거나 의존할 일이 없습니다.
만약 결합도가 높다면 의도하거나 잘못된 코딩일 가능성이 큽니다.

Observer패턴은 대부분 동기(synchronous) 방식으로 동작하나 Pub-Sub패턴은 대부분 비동기(asynchronous) 방식으로 동작합니다.##

이유는 Broker로 MessageQueue를 많이 사용하기 때문입니다.

Observer패턴은 단일 도메인 하에서 구현되어야 하나 Pub-Sub패턴은 크로스 도메인 상황에서도 구현 가능합니다.

이 역시 Broker라는 중간 매개체가 있기 때문인데 어플리케이션의 도메인이 다르더라도 MessageQueue(Broker)에 접근만 가능하다면 처리가 가능하기 때문입니다.

참고

Observer vs Pub-Sub pattern : https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c

HTML5(video) + Spring Boot(Tomcat)로 동영상 재생하기

|

HTML5의 video태그를 이용하여 파일 재생시 SpringBoot 에서 어떻게 설정해야하는지 간단한 방법이 있어 정리합니다.

샘플소스

기존 다른 방식들은 response에 파일을 직접 쓰도록 로직에 모두 구현을 했어야 하는데 StreamingResponseBody를 이용하여 아래와 같이 간단해집니다.

...
    private final String DIR = "${FILE_DIR}/";

    @GetMapping("/download")
    public StreamingResponseBody stream(HttpServletRequest req, @RequestParam("fileName") String fileName) throws Exception {
        File file = new File(DIR + fileName);
        final InputStream is = new FileInputStream(file);
        return os -> {
            readAndWrite(is, os);
        };
    }

    private void readAndWrite(final InputStream is, OutputStream os) throws IOException {
        byte[] data = new byte[2048];
        int read = 0;
        while ((read = is.read(data)) > 0) {
            os.write(data, 0, read);
        }
        os.flush();
    }
...

HTML 소스에서는 아래와 같이 호출합니다.

...
    <video controls src="/download?fileName=test.mp4">
        not use video
    </video>
...

원리

  1. 위 방식은 Progressive Download방식으로 서버에서는 요청시마다 전체 파일을 보내주고 video 태그에서는 점진적으로 필요한 만큼씩 OutputStream에서 읽어가게 됩니다. 실제로 readAndWrite 메소드의 while구문에서 로그를 찍어보면 동영상을 재생하지 않을 경우 write를 중간에 멈춰 있는 것을 볼 수 있습니다.

  2. StreamingResponseBody 클래스는 TaskExecutor을 이용하여 비동기 서블릿 실행을 지원해줍니다. Spring API 문서를 보면 아래와 같이 설명이 되어 있습니다.

A controller method return value type for asynchronous request processing where the application can write directly to the response OutputStream without holding up the Servlet container thread.
Note: when using this option it is highly recommended to configure explicitly the TaskExecutor used in Spring MVC for executing asynchronous requests. Both the MVC Java config and the MVC namespaces provide options to configure asynchronous handling. If not using those, an application can set the taskExecutor property of RequestMappingHandlerAdapter.
  1. 비동기이긴 하나 논블락킹은 아니기 때문에 별도 Thread를 계속 점유하고 있는 문제가 있습니다.
  2. 동영상 플레이를 하지 않고 대기시 async timeout 설정을 별도로 하지 않으면 중간에 연결이 끊겨버리며 DISPATCH_PENDING에러를 발생시킵니다. 또한 video태그는 다시 동영상을 재생하기 위해 같은 URL을 또 호출하게 되고 서버는 처음부터 다시 파일을 보내게 됩니다.

참고

Streaming 기술 이해 : http://linuxism.tistory.com/1267 Spring API (StreamingResponseBody): https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBody.html Itube - Spring Boot Streaming Response Body : https://github.com/shazin/itube

거버넌스(Governance) 모델 정리

|

조대협님의 “대용량 아키텍처와 성능 튜닝”중 거버넌스 모델을 공부하고 생각을 정리하는 차원에서 요약해봅니다.
정확한 정보및 개념은 “대용량 웹서비스를 위한 마이크로 서비스 아키텍쳐의 이해:http://bcho.tistory.com/948” 를 참고하세요.

거버넌스(Governance)란?

  • 시스템을 개발하는 조직구조 / 프로세스를 정의한 것.

중앙 집중형 거버넌스 모델

  • 중앙에서 표준 프로세스 및 규약, 정책을 내려줌.
  • 모두 동일하게 개발하기 때문에 유지보수 편함.

분산형 거버넌스 모델

  • 각 서비스에서 자체 규약으로 개발.
  • 표준 API만 외부로 노출.

분산형 거버넌스 모델을 수행하기 위한 팀구조의 특징은 아래와 같습니다.

Cross functional team

  • 필요한 모든 역활의 인원을 한 팀으로 묶음.
  • 타팀에 대한 의존성이 낮아지기 때문에 빠름 개발 가능.

DevOps

  • 개발 + 운영
  • 피드백에 따른 서비스 개선
  • 인프라까지 조절할 수 있어 개선에 따른 저항은 줄어들었으나 난이도는 높아짐

Project(X), Product(O)

  • 프로젝트별로 팀원 투입이 아닌 프로덕트별 구성.
  • 팀원이 해당 프로덕트에 영속됨으로써 지속적인 서비스 개선과 재교육에 대한 비용을 줄임.

위 모든 조건이 가능해지면 자체적으로 기획/개발/운영이 가능해지는 Self-oranized team 모델이 됨.

주의점 : Alignment

  • 팀 간의 수준 격차를 맞춰야 함 : 특정 서비스는 개발속도를 못따라옴
  • 최소한의 표준이 필요

Conclusion

MSA를 실현하기 위한 조직 구조로 분산형 거버넌스 모델운영에 대해 가이드를 하신것 같습니다.
빠른 서비스 개발및 런칭을 위한 것이 cross functional team인 것 같고, 그 이후 지속적인 개선과 운영을 위한 조직으로 devops를,
이를 효과적으로 운영하기 위해 팀원을 Project가 아닌 Product별로 배치하도록 하며, 주의점으로 팀간 수준을 맞추거나 최소한의 표준정책등을 지정하여 효율적인 운영을 할 수 있다고 요약하면 될 것 같습니다.

SpringBoot + ES6 + React 기반 보일러플레이트 소스

|

SpringBoot/ES6/React 기반으로 개발시 기반 소스로 바로 사용할 수 있는 보일러플레이트입니다.
해당 소스에는 간단한 React 샘플이 포함되어 있습니다.

소스를 직접 확인하고 싶으시면 다음 주소에서 확인하시기 바랍니다.

GitHub URL : https://github.com/jistol/boilerplate-boot-es6

Specification

  1. Backend
    • SpringBoot 2.0.0.RELEASE (Spring MVC, Embedded Tomcat, Thymeleaf)
  2. Frontend Builder
    • Package manager : npm
    • Bundler : webpack
  3. ES6 Package
    • babel
    • react
    • jquery
    • bootstrap
    • sass
    • webpack-dev-server

Setup & Run

Git 설치 및 소스 다운로드

Git 설치주소 : https://git-scm.com/downloads

소스 다운로드

$ git clone "https://github.com/jistol/boilerplate-boot-es6.git" boilerplate-boot-es6.git

npm 설치 및 초기화

npm은 Node.js를 설치시 같이 설치 가능합니다.

Node.js 설치주소 : https://nodejs.org/en/

설치 후 ROOT폴더에서 아래 명령어를 통해 초기화를 실행합니다.

$ npm install

위 명령어를 실행하면 node_modules 폴더가 생기면서 package.json에 포함된 라이브러리들이 다운로드 됩니다.

Backend 서버 실행

Gradle 빌드를 통해 WAR파일을 생성하여 직접 실행 가능하나 SpringBoot를 실행 할 수 있는 Gradle Task 명령으로 실행하여 서버를 기동할 수 있습니다.
Gradle Wrapper가 소스에 포함되어 있으므로 별도의 설치 과정없이 아래와 같이 실행 가능합니다.

$ ./gradlew bootRun

서버는 기본적으로 8080 포트로 접근 가능합니다.

ES6 소스 변환 및 Frontend Dev서버 실행

일반적으로 ES6를 지원하는 브라우저에서 실행하거나 babel을 통해 호환 가능한 소스로 빌드 후 실행할 수 있는데, 본 소스는 후자의 케이스로 실행하도록 샘플이 작성되어 있습니다.
변환은 아래와 같이 package.json에 npm 명령을 통해 실행 할 수 있도록 정의되어 있습니다.

...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "npm run build-js",
    "build-js": "./node_modules/.bin/webpack --config webpack.config.babel.js",
    "dev": "NODE_ENV='local' ./node_modules/.bin/webpack-dev-server"
  },
...

변환 실행은 아래와 같이 실행가능합니다.

$ npm run build

또한 빠른 개발을 위해 변경사항을 바로 반영하기 위해서 webpack-dev-server를 실행 할 수 있습니다.
webpack-dev-server에 대한 정의는 webpack.config.babel.js 파일의 아래 부분에서 확인 가능하며 NODE_ENV 값이 ‘local’ 일 경우에만 동작하도록 설정되어 있습니다.

...
    if (process.env.NODE_ENV == 'local') {
        let url = `localhost`,
            protocol = `http`,
            devPort = 8090,
            proxyPort = 8080,
            demoEntry = {
            };

        config.entry = Object.assign({}, demoEntry, config.entry);

        config.devtool = 'inline-source-map';
        config.devServer = {
            inline: true,
            hot: true,
            historyApiFallback: true,
            host: url,
            port: devPort,
            proxy: {
                "!/dist/js/**": `${protocol}://${url}:${proxyPort}`,
                secure: false,
                changeOrigin: true,
            }
        };

        Object.keys(config.entry).forEach(key => {
            config.entry[key].push(`react-hot-loader/patch`);
            config.entry[key].push(`webpack-dev-server/client?${protocol}://${url}:${devPort}`);
            config.entry[key].push('webpack/hot/only-dev-server');
        });

        config.plugins.push(new webpack.HotModuleReplacementPlugin());
    }
...    

아래 명령을 통해 실행 할 수 있습니다.

$ npm run dev

webpack-dev-server를 사용하기 위해서는 8090 포트로 접근하여야 합니다.

서버 기동후 아래 URL로 접속하면 index 페이지를 볼 수 있으며 링크 클릭시 간단한 React 샘플 예제를 확인 할 수 있습니다.

URL : http://localhost:8090

index page

react sample

Spring MVC 생명주기

|

Spring 처음 접할때 공부하고 정형화된 틀 안에서 쓰다보니 어떤 구조로 동작하는지 잊고 쓰다가 한 번 소스까서 보면서 정리해봅니다. 특히 정말 SpringBoot 기반으로 개발을 하니깐 DispatchServlet 조차 처음 보는것 같더군요.

MVC Structure

다른 웹페이지 자료들을 보면 좀 모양새가 다른데 소스 기준으로 정리하다보니 이런 구조로 그렸네요.

요청 순서

  1. 요청받은 Request로부터 실행할 Controller 추출을 위해 HandlerMapping 을 통해 실행할 Handler및 Interceptor를 전달

  2. Interceptor의 preHandle을 실행

  3. HandlerAdapter에 Handler를 전달하여 해당 Controller의 Argument매핑및 Method Invoke 실행하고 결과를 ModelAndView 형태로 반환

  4. Interceptor의 postHandle을 실행

  5. Resolver를 통해 실제 보여줄 View를 렌더링하여 Response에 Write

  6. Interceptor의 afterCompletion 을 실행