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세트)라는 가정 하에 아키텍쳐
- stand-alone : 독립된 형태, 데이터베이스가 동작하는 머신이 네트워크에 접속되어있지않음, 직접가서 작업을 해야하고 1번에 1명씩만 사용가능함
- 완벽하게 보안을 유지해야한다면 네트워크에 접속하지않고 사용(?)
- 클라이언트 - 데이터베이스(2개 레이어) : stand-alone에서 네트워크에 연결한 형태, 동시처리가 가능, 물리적으로 떨어져있지만 사용가능
- LAN만 사용 : 완전히 외부에서 접근 가능하게한다면 보안상의 문제가
- 클라이언트 - 서버 - 데이터베이스(3개 레이어) : 클라이언트가 직접 데이터베이스에 접근하지않고도 데이터를 받아올 수 있음
- 외부에서도 데이터에 접근할 수 있게됨 : 실제로는 서버 계층을 통해서 데이터베이스에 접근하는 것 - 직접적으로 접근하지않고도 데이터에 접근할 수 있게됨
- 여전히 에러가 발생했을 때, 성능이 한계선까지 왔을 때 교체 이외 방법은 없음
가용성을 높이기위해 다중화
- 다른 어플리케이션에 비해 다중화할 때 고려해야할 것이 많음 : 영속(데이터 보존)계층이어서 잘못되었다가 큰 이슈가 발생할 수 있음
- 다중화했을 때 데이터 동기화 이슈가 가장 큼 : 예를 들어 같은 데이터를 동시에 접근해서 각각 변경했을 때 어떻게 할 것인가?
- 심장(단 하나) 전략 : 소수 정예로 운영하되, 높은 비용을 들여 최고 사양의 시스템을 사용하는 것 - 그만큼 에러 처리, 다운되었을 때 복구 전략이 잘되어있다는 것인 듯
- 신장(여유분) 전략 : 다수로 운영, 소모품의 관점으로 바라봄, 동일한 기능의 컴포넌트를 복수개 준비, 대부분 이 전략을 사용함 - 크게는 클러스터링과 리플리케이션 전략으로 나눌 수 있음
- DB 서버는 다중화 데이터 저장소는 하나 : 클러스터링, shared disk(하나에 저장소에 접근하는 여러 DB서버 - DBMS 설치되어있는 컴퓨터), 같은 저장소에 여러 DB 서버가 접근할 때 병목현상이 발생할 수 있음을 신경써야함, 유일하게 사용되는 저장소가 사용할 수 없는 상태가 된다면?
- DB 서버, 데이터 저장소(저장소마다 각각이 저장하는 데이터가 다름) 하나씩 여러개 : 클러스터링, shared nothing(구글에서 개발한 자체 구조를 sharding이라고 한다고 함), 서로 저장하는 데이터가 각각 다름 - 성능 상의 이점(병목 현상 해결 - 많은 처리 분산)을 얻기위해서 사용하는 전략, 다운되었을 때를 대비해서 복제한다면(커버링 구성이라고 함)까지 사용한다면 엄청난 비용과 복잡한 구조가…
- DB 서버와 데이터 저장소 다중화 : DB 서버마다 각각의 저장소를 가지도록 함 - 복제(리플리케이션, 마스터 - 슬래이브 전략), 하나의 DB 서버 - 저장소 세트가 불능이 되더라도 다른 세트가 처리할 수 있음(단 물리적으로 떨어진 거리에 두는 것이 안전함), 갱신된 데이터를 모든 저장소가 맞춰야한다는 점(sync 전략, 정합성)을 고려해야함