본문 바로가기

아키텍처 Architecture/Software Architecture

책임연쇄패턴을 통한 로그 파싱 (chain-of-responsibility pattern)

1. 다양한 형태의 로그를 파싱할때

온프레미스, SCP, AWS, Azure, GCP 등 다양한 인프라에서 시스템을 운영하는 경우, 

하나의 대시보드에서 주요 로그와 metric을 확인하기위해 

로그 파싱을 위한 별도의 어플리케이션이 필요하다.

 

기존에 시스템의 규모가 크지 않을때는 shell 파일, 또는 단일 java 파일로 rule based 로직을 통해 충분히 

필터링 후 DB에 데이터를 넣을 수 있었지만, 사용하는 WAS, DB의 종류가 다양해지면서 

log의 파싱이 점차 복잡해졌다. 

 

향후 확장성과 코드의 가독성을 고려해 디자인패턴을 도입해 개선하기로 했고 찾아보던 중 

책임 연쇄 패턴 (Chain of Responsibility Pattern)을 발견했다.

 

https://ko.wikipedia.org/wiki/%EC%B1%85%EC%9E%84_%EC%97%B0%EC%87%84_%ED%8C%A8%ED%84%B4

 

책임 연쇄 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 객체 지향 디자인에서 책임 연쇄 패턴(chain-of-responsibility pattern)은 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다. 각각의 처리 객체는 명령 객체를

ko.wikipedia.org

 

쉽게 생각해보면 try catch 문을 생각하면 된다. 

각각의 step 에서 필요한 filtering 로직을 실행할 수 있고, 경우에 따라 filter를 여러개 꼈다 뺏다 할 수 있다.

대략적으로 2가지 효과를 고려했다.

① 각 필터의 디커플링 - 확장성, 독립성 보장 (추가, 수정, 삭제가 용이)

② 클린 코드에 부합 - 필터 로직을 캡슐화할 수 있음

 

2. CDC 프로그램을 만들때

https://subbak2.com/93

 

[Oracle] OGG란? CDC솔루션 쓰는 이유

1. CDC 솔루션의 필요성 - CDC란? Change Data Capture. 변경되는 데이터를 실시간으로 다른 데이터베이스에 동기화 시켜주는 기술을 의미한다. 예를 들어 A 서버에 있는 테이블의 데이터가 변경되면, 실

subbak2.com

 

서비스가 성장하면 시스템과 아키텍처의 복잡도는 증가할수밖에 없다.

이때 다양한 종류의 RDB(Oracle, MariaDB, PostgreSQL 등), NoSQL, Document DB 를 사용할 수 있고 

하나의 데이터를 다양한종류의 DB에 Insert 해야할 수 있다.

 

이 때 책임연쇄패턴을 통해 각 DB에 필요한 filter들을 정의해 데이터를 정제해 Insert할 수 있다.

 

예를 들면, Oracle에서는 ROWNUM 을 통해 결과의 수를 제한하고, MariaDB와 PostgreSQL은 LIMIT 를 통해 결과의 수를 제한한다.

각각 ROWNUM Filter, LIMIT Filter를 만들어 Handler에서 필요한 Filter를 조합해서 쓰는 경우, 

효율적인 코드작성이 가능하다.

 

3. Java 구현 예시

* 패키지 구조

├── chainofresponsibility
│   ├── FilterChainBuilder.java                 # filter를 조합하는 빌더
│   ├── FilterChainConfig.java                  # bean을 만들기위한 설정파일
│   ├── FilterHandler.java                      # filter 인터페이스    
│   ├── impl                                    # 각 기능별 filter
│   │   ├── TrimFilterHandler.java              
│   │   ├── SpecialCharactersFilterHandler.java  
│   │   ├── ...

 

* FilterChainBuilder.java

public class FilterChainBuilder {
    public static FilterHandler buildChain() {
        // Create instances of handlers
        FilterHandler trimHandler = new TrimFilterHandler();
        FilterHandler specialCharactersHandler = new SpecialCharactersFilterHandler();
        
        // Set the chain of responsibility
        trimHandler.setNext(specialCharactersHandler);
        
        return trimHandler; 
    }
}

 

* FilterChainConfig.java

@Configuration
public class FilterChainConfig {

    @Bean
    public FilterHandler filterChain() {
        return FilterChainBuilder.buildChain();
    }
}


* FilterHandler.java

public interface FilterHandler {
    void setNext(FilterHandler handler);
    String handle(String input);
}


* TrimFilterHandler.java

public class TrimFilterHandler implements FilterHandler {
    private FilterHandler next;

    @Override
    public void setNext(FilterHandler handler) {
        this.next = handler;
    }

    @Override
    public String handle(String input) {
        input = input.trim(); // Trim whitespaces
        if (next != null) {
            return next.handle(input);
        }
        return input;
    }
}


* SpecialCharactersFilterHandler.java

public class SpecialCharactersFilterHandler implements FilterHandler {
    private FilterHandler next;

    @Override
    public void setNext(FilterHandler handler) {
        this.next = handler;
    }

    @Override
    public String handle(String input) {
        // Remove special characters
        input = input.replaceAll("[^a-zA-Z0-9]", ""); 
        if (next != null) {
            return next.handle(input);
        }
        return input;
    }
}

 

반응형