TIL - 0710

java-was

RequestLine/Headers/Body 생성자에서 처리하는 작업을 유틸리티 클래스를 만들기

  • 보기좋은 코드를 만들기위해서 인자를 처리하는 작업만을 하는 클래스를 따로 만들기
    • 상태값을 가지지않고 메소드 인자로 처리 대상을 받아 처리하기 : static 메소드
public class RequestUtils {

    public static List<String> splitRequestLine(String requestLine) {
        return Arrays.asList(requestLine.split(" "));
    }

    public static List<String> splitPathAndParams(String pathAndParams) {
        return Arrays.asList(pathAndParams.split("\\?"));
    }

    public static Map<String, String> splitQueryString(String queryString) {
        return splitValues(queryString, "&");
    }

    private static Map<String, String> splitValues(String target, String regex) {
        if (Strings.isNullOrEmpty(target)) {
            return Maps.newHashMap();
        }
        String[] token = target.split(regex);
        return Arrays.stream(token).map(t -> getKeyValue(t, "=")).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toMap(Pair::getKey, Pair::getValue));
    }

    public static Optional<Pair> splitHeader(String header) {
        return getKeyValue(header, ": ");
    }

    private static Optional<Pair> getKeyValue(String keyValue, String regex) {
        String[] token = keyValue.split(regex);
        if (token.length != 2) {
            return Optional.empty();
        }
        return Optional.of(new Pair(token[0], token[1]));
    }

    public static class Pair {
        final String key;
        final String value;

        Pair(String key, String value) {
            this.key = key;
            this.value = value;
        }

        public boolean isMatch(String key) {
            return this.key.equals(key);
        }

        public String getKey() {
            return key;
        }

        public String getValue() {
            return value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Pair pair = (Pair) o;
            return Objects.equals(key, pair.key);
        }

        @Override
        public int hashCode() {
            return Objects.hash(key);
        }
    }
}

HandlerMapper와 Controller(Handler), ControllerPool 생성하기

  • 컨트롤러 인스턴스를 가지고 있고, 요청이 들어왔을 때 요청에 따라 컨트롤러를 찾아 RequestHandler에 돌려주는 역할
    • Spring MVC에서 HandlerMappings와 같은 역할 : RequestHandler(DispatcherServlet 역할)는 중간자 역할만 함(Request, Controller, Response)
    • 상태값을 가지지도 않고, 여러가지 전략이 생길 것도 아니기때문에 static 메소드만 가지는 class로 만듦
    • Controller 찾기 : @RequestMapping 어노테이션 value와 uri 맵핑을 시켜서 맞는 컨트롤러를 찾음
class HandlerMapper {
    private static final ControllerPool controllerPool = ControllerPool.of();

    static Controller mapHandler(String requestPath) {
        return search(parseControllerName(requestPath));
    }

    private static Controller search(String pattern) {
        return controllerPool.search(pattern);
    }
}

class ControllerPool {
    private static ControllerPool container = new ControllerPool();

    private final Map<Class<? extends Controller>, ? extends Controller> POOL;

    private ControllerPool() {
        POOL = ImmutableMap.of(
                HomeController.class, HomeController.of(),
                UserController.class, UserController.of()
        );
    }

    static ControllerPool of() {
        return container;
    }

    Controller search(String pattern) {
        Class<? extends Controller> controllerClass = (Class<? extends Controller>) POOL.keySet().stream().filter(clazz -> clazz.getAnnotation(RequestMapping.class).value().contains(pattern)).findFirst().orElseThrow(HandlerMappingException::new);
        return POOL.get(controllerClass);
    }
}
  • 만들다보니 프레임워크 사용 약속을 지켜야하는 이유를 알겠음 : 약속이 없다면 무수히 많은 동작 중에 어떻게 프레임워크 그 행동인지 캐치해내겠는가?
    • @RequestMapping을 달아두고, uri 맵핑을 해둬야함 : 요청이 왔을 때 어떤 컨트롤러와 맵핑해줄지 알아야함

database

데이터베이스 아키텍쳐 알아보기

1대(DB 서버 - 저장소 1세트)라는 가정 하에 아키텍쳐

  1. stand-alone : 독립된 형태, 데이터베이스가 동작하는 머신이 네트워크에 접속되어있지않음, 직접가서 작업을 해야하고 1번에 1명씩만 사용가능함
    • 완벽하게 보안을 유지해야한다면 네트워크에 접속하지않고 사용(?)
  2. 클라이언트 - 데이터베이스(2개 레이어) : stand-alone에서 네트워크에 연결한 형태, 동시처리가 가능, 물리적으로 떨어져있지만 사용가능
    • LAN만 사용 : 완전히 외부에서 접근 가능하게한다면 보안상의 문제가
  3. 클라이언트 - 서버 - 데이터베이스(3개 레이어) : 클라이언트가 직접 데이터베이스에 접근하지않고도 데이터를 받아올 수 있음
    • 외부에서도 데이터에 접근할 수 있게됨 : 실제로는 서버 계층을 통해서 데이터베이스에 접근하는 것 - 직접적으로 접근하지않고도 데이터에 접근할 수 있게됨
    • 여전히 에러가 발생했을 때, 성능이 한계선까지 왔을 때 교체 이외 방법은 없음

가용성을 높이기위해 다중화

  • 다른 어플리케이션에 비해 다중화할 때 고려해야할 것이 많음 : 영속(데이터 보존)계층이어서 잘못되었다가 큰 이슈가 발생할 수 있음
    • 다중화했을 때 데이터 동기화 이슈가 가장 큼 : 예를 들어 같은 데이터를 동시에 접근해서 각각 변경했을 때 어떻게 할 것인가?
  1. 심장(단 하나) 전략 : 소수 정예로 운영하되, 높은 비용을 들여 최고 사양의 시스템을 사용하는 것 - 그만큼 에러 처리, 다운되었을 때 복구 전략이 잘되어있다는 것인 듯
  2. 신장(여유분) 전략 : 다수로 운영, 소모품의 관점으로 바라봄, 동일한 기능의 컴포넌트를 복수개 준비, 대부분 이 전략을 사용함 - 크게는 클러스터링과 리플리케이션 전략으로 나눌 수 있음
    • DB 서버는 다중화 데이터 저장소는 하나 : 클러스터링, shared disk(하나에 저장소에 접근하는 여러 DB서버 - DBMS 설치되어있는 컴퓨터), 같은 저장소에 여러 DB 서버가 접근할 때 병목현상이 발생할 수 있음을 신경써야함, 유일하게 사용되는 저장소가 사용할 수 없는 상태가 된다면?
    • DB 서버, 데이터 저장소(저장소마다 각각이 저장하는 데이터가 다름) 하나씩 여러개 : 클러스터링, shared nothing(구글에서 개발한 자체 구조를 sharding이라고 한다고 함), 서로 저장하는 데이터가 각각 다름 - 성능 상의 이점(병목 현상 해결 - 많은 처리 분산)을 얻기위해서 사용하는 전략, 다운되었을 때를 대비해서 복제한다면(커버링 구성이라고 함)까지 사용한다면 엄청난 비용과 복잡한 구조가…
    • DB 서버와 데이터 저장소 다중화 : DB 서버마다 각각의 저장소를 가지도록 함 - 복제(리플리케이션, 마스터 - 슬래이브 전략), 하나의 DB 서버 - 저장소 세트가 불능이 되더라도 다른 세트가 처리할 수 있음(단 물리적으로 떨어진 거리에 두는 것이 안전함), 갱신된 데이터를 모든 저장소가 맞춰야한다는 점(sync 전략, 정합성)을 고려해야함