728x90

이진 트리 순회

불과 반년전만 해도 이름만 들었지 마냥 먼곳에 있다고 생각했던 자료구조들이다.
근데 공부하면서 깨닫는 것은 뭐를 알아야 준비를 하고 공부도 하고
재밌게 문제도 풀 수 있다는 것이다. 그것이 바로 코딩테스트 😱
DFS니 BFS니 하려면
일단 스택, 큐, 배열, 재귀에 대해서 알아야된다고 생각했다.
물론 그리고 지금 포스팅하는 이 이진 트리에 대해서도 좀 짚고 넘어가야 한다고 봤다.

이진트리란?

이진트리는 각각의 노드가 아래 자식 노드를 최대 두개를 가진 트리 자료 구조이다.

이진트리 예시

위 이미지는 위키백과 에서 가져와봤다.
깊이(depth)는 3이고 크기는 9인 이진트리이다.

     1
  2     3
4  5   6  7

이런식으로 구성된 트리가 있을 때 전위 표기식으로 순서를 나타내는 알고리즘을 구성해보자

코드

public class Main {
    private static class Node {
        private int value;

        private Node left;
        private Node right;

        public Node(int value) {
            this.value = value;
            left = right = null;
        }
    }

    private static void dfs(Node n) {
        if (n == null) {
            return;
        }

        System.out.print(n.value + " ");
        dfs(n.left);
        dfs(n.right);
    }

}

자기 자신의 노드 그리고 left, right의 자식 노드를 알고있어서 재귀로 다음 노드를 호출하며

null인 경우에는 바로 return을 해주어 바로 다음 로직으로 이동하게끔 구현이 되었다.

전위 순회로 출력하게 되면 1 2 4 5 3 6 7 순서로 나오게 된다.

728x90

'CS > 자료구조' 카테고리의 다른 글

자료구조  (0) 2022.08.10
728x90

@ExceptionHandler

예외 처리기가 어떻게 동작하는지에 대해서 궁금했어서
업무중에 돌려보게 되었다. (예제코드는 다시 작성할 예정)
일단 동작과정은 DB에서 해당 id를 찾아 검색했을 때 없을 경우 예외를 던져주게 하는

예시

간단하게 보면

public class UserService {
    private final UserRepository userRepository;

    public User findById(final int id) {
        return userRepository.findById(id)
                             .orElseThrow(() -> new NotFoundException("해당 유저를 찾을 수 없습니다"));
    }
}

라고 로직을 구성했을 때 이 로직의 예외에 대한 핸들러 동작을 파보게 되었다.
일단 get 메소드로 조회 로직을 수행하고

그 요청을 RequestMappingHandlerAdapter로 위임해서 처리를 요청한다.

그다음 주어진 값들로 컨트롤러에 대한 로직을 처리하는데

InvocableHandlerMethod

다음 그림이 InvocableHandlerMethod 클래스이다.

스크린샷 2022-02-09 오전 11 14 03

이 때, getBridgedMethod().invoke(getBean(), args); 부분이

cglibAopProxy쪽으로 조회 로직을 맡기게 되고 그 곳에서 repository에 대한 동작을 수행하다가
findById에서 못찾았을 경우에 에러를 던지게 된다.
다시 cglibAopProxy에서 요청결과에 대한 에러를 잡아서 다시 처리되는데

이것이 InvocableHandlerMethodcatch로 넘어온다.

catch (InvocationTargetException ex) 이부분에서

RuntimeException의 인자인지를 확인한다.

NotFoundException

이 부분에서 내가 구현한 NotFoundException 의 상속도는

NotFoundException -> NoSuchElementException -> RuntimeException -> Exception 순서기 때문에

instanceof RuntimeException 으로 처리가 되게 된다.

그렇게해서 exception 객체를 담아서 DispatcherServlet 으로 넘겨주게 된다.

DispatcherServlet

이젠 디스패쳐 서블릿이 받은 에러를 처리해줄 누군가를 찾기 시작하는데

스크린샷 2022-02-09 오전 11 18 55

HandlerExceptionResolverComposite

스크린샷 2022-02-09 오전 11 19 56

이 두 개중 HandlerExceptionResolverComposite 로 처리를 진행해준다.

그렇게 해서 resolveException 메소드를 실행해주는데

다음 이미지를 보게되면 resolvers에 3개가 들어있게 된다.

스크린샷 2022-02-09 오전 11 20 44

그 리졸버들이 바로 HandlerExceptionResolver 들을 구현한것들

스크린샷 2022-02-09 오전 11 22 39

그중에서도 나는 ResponseStatusException을 날려준게 아니기 때문에

그중에서 최종적으로 HandlerExceptionResolver 인터페이스 구현체인

ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver

세개를 가지고 for문을 돌게 된다

InvocableTargetException 이라는 객체의 target

즉, 대상이 어떤 Exception이냐에 따라서 Exception 에맞는 @ExceptionHandler로 파싱되어 커스텀된 응답으로 나가게 된다.

마무리

똑같은 로직 이라고 가정했을때 미묘하게 최적화를 하고싶다? 한다면

HandlerExceptionResolver 리스트의 맨앞인 ExceptionHandlerExceptionResolver를 사용 그러니까

커스텀으로 @ExceptionHandler 를 만들어 쓰는것이 ResponseStatusException 을 던지거나, @ResponseStatus 을 사용하는것보다

빠를 수 있겠다.

728x90

'Spring' 카테고리의 다른 글

@Valid, @Validated 차이  (0) 2022.08.10
AOP  (0) 2022.08.10
Spring Rest Docs 테스트로 문서화를 해보자!  (0) 2022.08.10
Service Layer에 대한 생각  (0) 2022.08.10
728x90

선택 정렬 (Select Sort)

선택 정렬은 현재 위치에 들어갈 데이터를 찾아 선택하는 알고리즘이다.
오름차순을 기준으로 정렬한다.

개념

제자리 정렬의 알고리즘 중 하나이다.
정렬 되지 않은 입력된 배열 외에 다른 메모리를 사용하지 않는다.
해당하는 n번째에 넣을 정렬된 원소 자리는 이미 정해져있고,
어떤 값을 넣을지를 선택하는 알고리즘이다.

동작 과정

  1. 주어진 배열에서 최솟값을 찾는다.
  2. 그 최솟값을 배열의 맨 앞의 수와 자리를 교체해준다.
  3. 맨 처음 값을 뺀 나머지 배열로부터 최솟값을 찾는다.
  4. 교체한 다음 맨 앞의 배열과 값을 바꿔준다.
  5. 이 과정을 정렬이 완료될 때까지 계속 반복한다.

스크린샷 2022-02-07 오후 10 19 41

보기 좋은 예시 이미지를 가져와봤다.
이제 그러면 구현을 해보도록 하자.

Select Sort 구현

n 길이를 가진 배열을 생성하고 다음줄에 n개의 숫자를 입력받아 선택정렬 한다.

public class SelectionSort {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine()); //길이가 n
        StringTokenizer st = new StringTokenizer(br.readLine());

        int[] arr = new int[n]; //n개의 숫자를 넣을 배열

        for (int i = 0; i < n; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        final int[] results = solution(arr);

        for (int result : results) {
            System.out.print(result);
        }
    }

    private static int[] solution(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }

            //최소값과 해당 반복 index의 맨 앞자리와 치환
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        return arr;
    }
}

결론

이런 사소한 지식이라도 안적어두면 까먹고 또 기억이 안나는것 같다.
위의 예시도 ide도움 없이 손코딩으로 포스팅을 올려보는데 더 상기되서 까먹지 않을것 같다.
계속 까먹기 때문에 이렇게 기록으로 남겨두는것이 좋을듯 하다. 😎

728x90

'CS > 알고리즘' 카테고리의 다른 글

에라토스테네스의 체  (0) 2022.08.09
728x90

자료구조

자료 구조의 사전적인 의미는,
효율적인 접근 및 수정을 가능케하는 자료의 조직, 관리, 저장을 의미한다.
그래서 이 자료구조는,
데이터들의 값 모임, 데이터간 관계 들을 의미한다.
이 사전적 의미를 보니까 알고리즘과 뗄래야 뗄 수가 없는거 같다.
결국 이 선택에 따라 효율적으로 알고리즘을 설계를 할 수 있으니까 말이다.

들어가며

이 자료구조들은 당연히 여러 가지가 있으며, 각각의 자료구조는 각자의 연산과 목적에 맞춰져 있다.

단순히 알고리즘 문제들만 풀 때 자료구조를 선택한다? 답은 NO

어떠한 기능을 설계함에 있어 자료구조를 선택하는 것은 필수일 것이다.
자꾸 예를 알고리즘으로 들어서 그렇지만, 이만큼 알고리즘과 뗄래야 뗄 수가 없다는것.
자료구조가 명확해지면, 그에 따라오는 알고리즘이 반드시 필요한게 있을 거라고 본다.

종류

자료의 특성, 크기, 사용법, 연산에 따라 여러가지 종류가 있다.
크게 단순 구조, 선형 구조, 비선형 구조, 파일 구조로 나눌 수 있다.

스크린샷 2022-02-01 오후 9 42 40

단순 구조

  • True/False, 정수, 실수, 문자 및 문자열과 같이 컴퓨터가 기본적으로 제공하는 자료형

선형 구조

  • Array(배열)
    • 가장 일반적인 구조
    • 메모리 상에 같은 타입의 자료가 연속적으로 저장
    • 자료값을 나타내는 가장 작은 단위
  • LinkedList
    • 노드를 하나의 단위로 한다.
    • 노드는 자료와 다음 노드를 가리키는 참조값으로 구성
    • 점 조직 같은 느낌
  • Stack
    • 후입선출(Last-In-First-Out)
    • 먼저 저장된 것이 마지막에 나오게 되는구조
    • 자료의 나열 순서를 바꾸고 싶다면 스택에 넣었다가 꺼내면 역순으로 변경된다.
  • Queue
    • 선입선출(First-In-First-Out)
    • 먼저 저장된 것이 먼저 나오게 되는 구조
  • Deque
    • 양쪽에서 넣기 빼기를 할 수 있는 구조

비 선형 구조

  • Graph
    • 꼭짓점과 꼭짓점을 잇는 변으로 구성된다.
  • Tree
    • 뿌리와 뿌리 또는 다른 꼭짓점을 하나의 부모로 갖는 꼭짓점들로 이루어진 구조
    • 부모와 자식의 관계는 변으로 표현

파일 구조

하드디스크 같은 보조 기억장치에 저장되는 파일에 대한 자료구조
메모리에 한번에 로드할 수 없는 대용량의 자료
파일 구성 방식에 따라 순차, 색인, 직접으로 나뉘게 된다.

728x90

'CS > 자료구조' 카테고리의 다른 글

이진트리  (0) 2022.08.10
728x90

728x90

'Spring' 카테고리의 다른 글

AOP  (0) 2022.08.10
@ExceptionHandler  (0) 2022.08.10
Service Layer에 대한 생각  (0) 2022.08.10
Validaion  (0) 2022.08.09
728x90

회고록

블로그를 옮기게 되어 날짜가 맞지 않는다.

작성일 : 2022년 2월 21일

 

서비스 회사로 와서 벌써 1달 반정도가 지났다.
스타트업인지라, 먼저 있었던 사람들을 욕할건 아니다.
그렇지만 지금 그렇게 쳐나가면서 생겼던 기술부채로 인해서 신규 개발건이 들어왔을 때
유지보수가 힘든 점들이 많다.

무책임했던 누군가

기존에 있던 백엔드 개발자가 나가고 없었기에 모르겠지만 그사람은

자바에 엄청난 자부심이 있다고 했다.

<<-- 이렇게 하면 안되지만 정말 못짰다..🤬

협업이라는 것을 싫어했고, 자기의 의견이 맞았으며, 코딩 컨벤션 또한 지켜진게 없었다.
그리고 외주로 뭔가 개발이 왔던건줄 알았던 코드가 그 사람의 코드였다...

악취나는 코드의 예시들

클래스가 MAIN_CLASS, main_class, MainClass, mainClass 등등... 네이밍이 상당했다.

이걸 나의 사수분께서는 JDBC -> JPA로 리팩토링을 하고 계셨던 찰나에 내가 입사를 했던 것이다.
많이 봐서 익숙했던 HTTP 상태 코드도 쓰지 않고 커스텀으로 200을 정의했는데 이게 에러였나? 그랬다.

switch(STATUS_CODE) {
    case 200: 
    case 201: 
    case 202: 
    case 203: 
    case 204: 
    // 이렇게 엄청 많이 있었다
}

이렇게 아마 300번까지 있나 그런다.. 너무 보기싫다.. 😇

아니 ResponseBody를 전역에서 설정해준것이 바로 @RestController가 아닌가?

난 내 지식이 의심스러울 정도였다.
간략하게나마 써보자면...
예를들어 유저를 조회하려는 API가 있다고 하면..

 

나름의 컨벤션이 있던건 같다. 클래스 뒤에 1은 create, 2는 read, 3은 delete, 4는 update였다 ㅋㅋㅋㅋ

 

아직도 웃기다.. 🤣🤣🤣🤣
아 아래 예시는 조회인데 조회 API 클래스만 따로있다.
그러니까 위에서 했던 1, 2, 3, 4 일련의 동작들이 하나로 엮인게 아니라
1기능 당 1개의 컨트롤러다.

@RestController
public class h_user_info_2 {

    @ResponseBody
    @RequestMapping(value = "/pood/user/view/2", method = RequestMethod.POST)
    public String USER_INFO_LIST_VIEW(@RequestBody h_user_info_vo2 header) { // 이런식으로 받아온다.. + 조회인데 POST + api 뒤에 숫자도 붙는다..
        // 왜 실행하고 있는지 모르겠는 로직...
        // 뭐 대략 이런식으로 돌아간다
        return response;
    }
} 

아무튼 뭐 이런식으로 구성이 되어있었으니 상당히 머리아프고 파악도 못해먹겠다.
그래서 사수도 엄청 답답했을 것이다.
이걸 JPA로 바꾼 사수님은 👍👍👍
하지만 그래도 이거로는 부족했다.

객체지향적으로 코드를 구성해보자

일단 바꾼것, 가독성을 높인건 사실 맞다.

하지만, 여기서도 문제가 있었는데 흔히 보는 JPA의 Repository인 UserRepository (예시)

이 Repository를 어떤 서비스에서 필요로 할 때마다 의존 주입을 해서 쓰고있던것이다.

여기서 이제 이것도 나는 바꿔야 겠다고 마음을 먹었고, 하나의 서비스 -> 하나의 리파지토리 를 의존하는 것이 가장 좋다.

라고 생각을 했다.

그리고 여러 비즈니스 로직을 담는 클래스 -> 서비스 인것 같은 뉘앙스가 많이 풍겼다.

스크린샷 2022-01-26 오후 9 55 21

흔히 볼 수 있는 Layered Architecture 로 구성이 되어있다.

근데 참조가 너무 많은거다. 😭 이 상황에서 퍼사드 패턴을 떠올렸다. 🤔

구성

그래서 Controller -> Facade -> Service -> Repository 로 의존의 흐름을 넘기는 것을 생각했다.

그래서 여러 서비스 클래스를 한데모아 Facade에서 각 서비스들을 조합해서 붙여주는 식으로 정리를 했더니

의존 방향도 틀이 맞춰지고, 서비스단과 리파지토리 단위테스트를 쉽게 가져갈 수 있게 되었다.

@RestController
public class Controller {
    private final Facade facade;

    public Controller(final Service service) {
        this.service = service;
    }
}

@Facade // 어노테이션을 새로 정의해주었다
public class Facade {
    private final Service1 service1;
    private final Service2 service2;
    private final Service3 service3;

    public Facade(final Service1 service1, final Service2 service2, final Service3 service3) {
        this.service1 = service1;
        this.service2 = service2;
        this.service3 = service3;
    }
}

// 이런게 3개 있다.
public class Service1 {
    private final Repository1 repository1;

    public Service1(final Repository1 repository1) {
        this.repository1 = repository1;
    }
}

아무튼 이렇게 구성을 하게 되었다.

결과

결국 전체가 더러워질 바에는 서비스가 더러워져선 안된다고 생각을 했고,
많은 사람들에게 자문을 구한 결과도 다들 방금 한 얘기를 다들 하셨다. 감사합니다~ 🙏
무언가를 호출해서 모아주는 집약체인 퍼사드 클래스가 더러워지게 된거다.
기존 코드에 비해 엄청나게 개선 되었다고 생각한다. 중구난방인 의존을 한데 모았기 때문이다.
아무튼 한달반동안 많이 성장한것 같고 더 성장해야겠다.
갈 길이 멀고 나는 아직 배가 고프다. 더 많이 더 빨리 더 높게 성장하고싶다 👊 🔥🔥🔥🔥

728x90

'Diary' 카테고리의 다른 글

ATDD, 클린 코드 with Spring 5기 수료 회고  (0) 2022.08.14
블로그를 옮기고 최신 근황  (0) 2022.08.13
업무 회고  (0) 2022.08.07
1개월 1일 1커밋 회고  (0) 2022.08.07
728x90

모놀리식MSA에 대해 차이를 정리해 보겠다.

MSA가 등장하기 이전에 하나의 서비스로 하나의 애플리케이션을 만드는 것.

이게 바로 Monolithic Architecture(모놀리식 아키텍처) 라고 한다.

MSAMicro Service Architecture 의 줄임말로 하나의 큰 애플리케이션을

작은 애플리케이션으로 나눠서 만드는 아키텍쳐이다.

Monolithic Architecture

사전적 정의를 보면

스크린샷 2022-01-18 오후 10 55 13

단단이 짜여 하나로 되어있는 이라는 뜻으로 나와있다.

그렇기에 이 애플리케이션의 규모는 거대하다.

장점

  • 로컬 환경에서의 개발 편리성
  • 통합 시나리오 테스트 용이
  • 배포 간단

단점

  • 코드의 수정 및 추가가 힘들다
  • 효율적 자원관리가 힘들다
  • 자주 업데이트 불가능
  • 신기술 적용의 힘듦
  • 부분적인 서버의 장애 -> 전체 장애로 번짐
  • Scale Out 이 불가능하다

Scale Out이란?

서버를 운영중에 있을 때 사용자가 갑자기 급격하게 증가 할 때,
더 많은 서버 용량, 그리고 성능이 필요해지는데
이 때, 서버를 여러대 추가하여 확장시키는 방법을 말한다.
반대로 Scale Up 이 있는데,
이는 여러대를 추가하는 방식이 아니라 단순하게 보면
그냥 서버 컴퓨터의 성능을 증가시켜주는 것이다.

스크린샷 2022-01-18 오후 11 03 07

모놀리식의 장단점을 보면 단순하다.
모놀리식, MSA 모두 각각의 장단점이 있기 때문에 상황에 따라 달라질 수 있다는 점은 주의해야한다.
일단 하나의 애플리케이션이기 때문에 단순해서 좋다. 그래서 소규모 개발일 때는 모놀리식이 더 적합할 것 같다.
근데 이 프로젝트의 규모가 커진다면 단순해지면 안좋아 진다.

의존성

객체지향 프로그래밍을 하다보면 의존성이라는 단어는 참 많이 접하게 된다.
이것이 아키텍쳐에도 적용하여 생각하면, 너무 많은 기능들을 구현했고,
그 기능들이 하나의 애플리케이션에 묶여있으니까, 서로간의 의존성이 높아지고
이해할 수 없는 어려운 코드도 만들어 질 것이다.
결국 이런 문제를 빚어 커지면 커질 수록 고려하게 되는 것이 바로 MSA이다.

Micro Service Architecture

단어들만 봐도 의미를 알 수 있겠다.
현 회사에 도메인은 여러가지가 있다.
주문, 배송, 결제, 등등... 커머스이기 때문에 들어가는 당연한 도메인들이다.

이런 도메인들을 하나씩 분리하는 것이 바로 MSA

장점

  • 빌드 및 테스트 시간 단축
  • 유연한 기술 적용
    • 어떤 한 언어와 프레임워크에 종속되지 않는다.
  • Scale Out 가능
  • 서비스간 연관성 낮음
    • 한 서버의 문제가 다른 서비스에 영향을 끼치지 않는다.

단점

  • 성능 이슈
    • 모놀리식의 경우 다른 기능을 호출할 때 메소드를 호출한다.
    • MSA는 네트워크 비용이 발생하게 된다.
  • 트랜잭션
    • 다른 서버들간의 트랜잭션 처리를 할 경우 불편해질 수 있다.
  • 개발 시간 증가
    • 서버가 분리됨에 따라 관리가 필요해짐
    • 여러가지 신경을 많이 쓰며 관리해주어야 한다.

정리

결국 MSA가 좋다? 그건 아니다. 이렇게 놓고보니 정답도 없다.
이거는 알 수 있었다. 🤣
모든 것을 다 분리한다기 보다는
하나의 애플리케이션중 특정 서비스 부분의 트래픽이 월등하게 많다면? 🤔
그러면 그 부분을 따로 떼어내서 서버를 스케일 아웃해서 증가시키고
기준을 트래픽이로 잡아 분리하는 게 좋아보인다.
그리고 무엇보다 근본인 프로젝트, 회사 규모에 따라 상황에 맞는 적절한 아키텍쳐를 잡아가는 것이 바람직한 선택이다.

728x90

'아키텍처' 카테고리의 다른 글

DDD 표현 영역과 응용 영역  (0) 2022.08.11
DDD 도메인  (0) 2022.08.11
728x90

Service Layer

이직하고 프로젝트에 대해 구조 파악을 하면서 리팩토링을 진행중에 좀 생각하게 된게 꽤많았다.
사실 서비스 레이어라고 해서 비즈니스 로직을 다 넣는건가?
또는 비즈니스 로직이 다 들어가 있는 것이다. 라는 얘기들이 많았다.
사실 나도 그렇게 생각했었던 사람중 1명이었다.
이게 근데 잘못된 생각이었다.

결국엔 사실 소스 코드를 다 분리하고 봐도 하나로 합쳐져서 동작하게 되는건 사실 맞다고 본다.

그러니까 다시 말하면 클래스 하나의 메소드에서 엄청나게 많은 줄을 가지고 개발을 할 수도 있다는 것이다.
근데 이거는 객체 지향 설계 관점에선 너무 안좋은 것이고
각자의 책임이 있을 것인데 그걸 분리한게 객체 지향인거다.

Service Layer에 대한 오해

일단 이 서비스 레이어에서는 비즈니스 로직이 넘쳐나게 될 것이 아니라,

적어도 뭔가의 조건을 통해 돌려주고 수정하고 하는 로직들은 도메인 객체가 해야될 일이라는 것이다.

보통의 서비스 레이어 특징

  • @Service에 사용되는 서비스
  • 일반적으로는 @Controller, @Repository에 사용된다.
  • @Transactional 이 사용되는 영역

도메인 특징

  • 도메인이라고 불리는 대상이 뭘 하는 객체인지 모든 사람들이 알 수 있고 공유하게 만든 모델
    • 주문, 상품 등등.. 이 도메인이 될 수 있다.
  • JPA를 사용한다면 @Entity 모델이 될 수도 있다.
    • 그렇지만, DB의 테이블과 동일해야 한다? 에서는 NO라고 할 수 있다.

모아서 보니까 결국엔 비즈니스 로직은 도메인이 가져야 한다.

그렇다면 서비스 레이어는???

트랜잭션 관리, 도메인의 순서 대로 객체에게 할당을 하여 식만 조합해주는 느낌으로 가야한다.
그래서 나는 예시 소스로 간단하게 만들어보자면

public class ExampleService {
    private final ExampleRepository1 exampleRepository1;
    private final ExampleRepository2 exampleRepository2;
    private final ExampleRepository3 exampleRepository3;

    public ExampleService(ExampleRepository1 exampleRepository1,
     ExampleRepository2 exampleRepository2, ExampleRepository3 exampleRepository3) {
        this.exampleRepository1 = exampleRepository1;
        this.exampleRepository2 = exampleRepository2;
        this.exampleRepository3 = exampleRepository3;
    }
}

만약 이런 클래스가 있어서 각 repository별로 사용하는 트랜잭션이 다르다면 트랜잭션 처리가 애매했다.
위 구조를 아래와 같이 변경했다.

public class ExampleFacade {
    private final ExampleService1 exampleService1;
    private final ExampleService2 exampleService2;
    private final ExampleService3 exampleService3;

    public ExampleService(ExampleService1 exampleService1,
     ExampleService2 exampleService2, ExampleService3 exampleService3) {
        this.exampleService1 = exampleService1;
        this.exampleService2 = exampleService2;
        this.exampleService3 = exampleService3;
    }

}

@Transactional
public class ExampleService1 {
    private final ExampleRepository1 exampleRepository1;

    public ExampleService1(ExampleRepository1 exampleRepository1) {
        this.exampleRepository1 = exampleRepository1;
    }
}

@Transactional
public class ExampleService2 {
    private final ExampleRepository2 exampleRepository2;

    public ExampleService1(ExampleRepository2 exampleRepository2) {
        this.exampleRepository2 = exampleRepository2;
    }
}

@Transactional
public class ExampleService3 {
    private final ExampleRepository3 exampleRepository3;

    public ExampleService1(ExampleRepository3 exampleRepository3) {
        this.exampleRepository3 = exampleRepository3;
    }
}

이렇게 분리를 해서 트랜잭션 관리를 해줬었다.
이게 이제 이전에 내가 퍼사드 패턴 적용기 라고 해서 포스팅을 했었던 구조인데
거기에 +@ 로 덧붙인다면 각 도메인에 대한 로직 수행을 서비스 -> 도메인 이 과정을
넣어주어야 완성인 것 같다.
객체 설계에 대해 점차 이해가 쏙쏙 되는 중이다.

728x90

'Spring' 카테고리의 다른 글

@ExceptionHandler  (0) 2022.08.10
Spring Rest Docs 테스트로 문서화를 해보자!  (0) 2022.08.10
Validaion  (0) 2022.08.09
Filter, Interceptor 정리  (0) 2022.08.07
728x90

# 클라이언트 식별과 쿠키

이 장에서는 서버가 통신하는 대상을 식별하는데에 사용하는 기술을 알아본다.

## 개별 접촉

HTTP는 익명으로 사용하며 상태가 없고 요청(Request)과 응답(Response)로 통신하는 프로토콜

현대의 웹 사이트들은 개인화된 서비스들을 제공하고 싶어한다.

### 개별 인사

개인에게 맞춰져 있는 것처럼 느끼게 하려고 사용자에게 특화된 환영 메세지나 페이지 내용을 만듦

### 사용자 맞춤 추천

고객의 흥미가 무엇인지 학습해서 고객이 좋아할 만한 상품을 추천해준다.

개개인의 기념일이나 생일이 다가오면 그에 맞는 상품을 제시할 수도 있다.

### 저장된 사용자 정보

배송지 주소와 카드 정보를 매번 입력받게 하지말고

데이터베이스에 저장하여 저장하는 경우를 말한다.

### 세션 추적

HTTP 트랜잭션은 상태가 없다.

각 요청, 응답은 독립적으로 일어난다.

사용자가 사이트와 상호작용 할 수 있게 사용자의 상태를 남기는데,

여러 상태들을 유지하려면, 웹 사이트는 HTTP 트랜잭션을 식별할 방법이 필요하다.

- 사용자 식별 관련 정보를 전달하는 HTTP 헤더들
- 클라이언트 IP 주소 추적으로 알아낸 IP 주소로 사용자 식별
- 사용자 로그인 인증을 통한 사용자 식별
- URL에 식별자를 포함하는 뚱뚱한 URL
- 식별 정보를 지속해서 유지하는 쿠키

## HTTP 헤더

| 헤더 이름 | 헤더 타입 | 설명 |
|:------------|:-------|-------------------------|
| From | 요청 | 사용자의 이메일 주소 |
| User-Agent | 요청 | 사용자의 브라우저 |
| Referer | 요청 | 사용자가 현재 링크를 타고 온 근원 페이지 |

> From 헤더

사용자의 이메일 주소를 포함

고유한 이메일 주소를 갖기 때문에 이론상 From헤더로 사용자를 식별할 수 있다.

악의적 서버가 이 이메일 주소들을 모아 스팸메일을 발송할 수 있는 문제가 있다.

로봇이나 스파이더는 본의 아니게 웹 사이트에 문제를 일으킨 경우, 항의 메일을 보낼 수 있도록

From헤더에 이메일 주소를 기술한다고 함

> User-Agent 헤더

사용자가 쓰고 있는 브라우저의 이름과 버전정보, 등등을 서버에게 알려준다.

이는 특정 브라우저에게 특화된 컨텐츠를 최적화하는데 도움을 줄 수 있지만,

특정 사용자들을 식별하는 데는 큰 도움이 되지 않는다.

- 헤더 예시

<img width="904" alt="스크린샷 2022-01-08 오후 1 36 08" src="https://user-images.githubusercontent.com/74235102/148631546-27fa5967-75df-4055-b7aa-a8041352bf65.png">

> Referer 헤더

사용자가 현재 페이지로 유입하게 한 웹페이지의 URL을 가리킨다.

<img width="220" alt="스크린샷 2022-01-08 오후 1 45 45" src="https://user-images.githubusercontent.com/74235102/148631796-b615f35b-9b08-40c7-86dc-75d69e2bbf69.png">

이 헤더로 이전에 어떤 페이지를 방문했었는지 알 수 있다.

`왜?` - 여기까지 들어오기 직전의 URL을 파악할 수 있기 때문에 유추할 수 있게 된다.

---

하지만 이 세가지 헤더로 확실하게 뭔가를 식별하기엔 정보가 부족함.

## 클라이언트 IP 주소

사용자가 확실한 IP 주소를 가지고 있고, 그 주소가 바뀌지 않고, 웹 서버가 요청마다

클라이언트 IP를 알 수 있다면 문제없이 동작한다.

### 약점

**클라이언트 IP 주소는 사용자가 아닌, 사용하는 컴퓨터를 가리킴.**

여러 사람이 이용하는 경우 그들을 식별할 수 없다.

**인터넷 서비스 제공자는 동적으로 IP주소를 할당**

그렇기 때문에 사용자를 IP 주소로 식별할 수 없음

**보안을 강화하고 부족한 주소들을 관리하려고 NAT 방화벽을 통해 인터넷을 사용함.**

클라이언트의 실제 IP 주소를 방화벽 뒤로 숨기기 때문에 식별할 수 없음

**HTTP 프락시와 게이트웨이는 원서버에 새로운 TCP 연결**

이렇게 되면 실제 IP주소가 아닌 프락시의 IP주소를 보게 됨

## 사용자 로그인

IP 주소로 식별하는 수동적 방식보다,

웹 서버는 사용자 이름과 비밀번호 인증을 요구하여 명시적으로 식별할수 있다.

`WWW-Authenticate`, `Authorization` 헤더를 사용하여 사용자 이름을 전달하는 체계를 갖고 있음.

자세한 내용은 12장에서 다룰 예정

## 뚱뚱한 URL

어떤 웹 사이트는 사용자의 URL마다 버전을 기술하여 사용자를 식별하고 추적했다.

> 뚱뚱한 URL이란?

사용자의 상태 정보를 포함하고 있는 URL

사용자와 관련된 정보를 찾아서 밖으로 향하는 모든 하이퍼링크에 정보를 포함하기 때문에

뚱뚱한 URL이 되고,

이 뚱뚱한 URL은 심각한 문제들이 있다.

### 못생긴 URL

브라우저에 보이는 뚱뚱한 URL은 새로운 사용자에게 혼란을 준다.

### 공유하지 못하는 URL

당연한 얘기인것은, 이 뚱뚱한 URL에 사용자에 관련된 정보들이 포함되어 있다.

그렇기에 공유하는 순간 내 개인정보들도 같이 공유한다는 것이 된다.

### 캐시를 사용할수 없음

URL로 만드는 것은, 계속해서 요청되는 URL이 변경되기 때문에 기존 캐시에 접근할 수 없다.

### 서버 부하 가중

서버는 뚱뚱한 URL에 해당하는 HTML 페이지를 다시 그려야 한다.

### 이탈

URL에 정보들이 추가된것만 사용해야 문제없이 동작한다.

하지만 중간에 사용자가 이탈하게 되면, 진행상황들이 다 리셋이 되는 경우가 발생할 것이다.

### 세션 간 지속성의 부재

특정 URL을 저장해놓지 않는 이상은, 로그아웃하게 되면 정보를 전부 잃는것이 된다.

## 쿠키

쿠키는 사용자를 식별하고 세션을 유지하는 방식 중, 현재 가장 널리 사용하는 방식

쿠키는 넷스케이프에서 개발했지만, 지금은 모든 브라우저에서 지원한다.

쿠키는 캐시와 충돌할 수 있어서, 대부분의 캐시나 브라우저는 쿠키에 있는 내용물을

캐싱하지 않는다.

### 쿠키의 타입

쿠키는 세션쿠키, 지속쿠키 두가지 타입이 존재

세션 쿠키 - 사용자가 브라우저를 닫으면 삭제되는 쿠키

지속 쿠키 - 삭제되지 않고 더 길게 유지될 수 있는 쿠키

두개의 차이점은 **파기되는 시점**

파기까지 남은 시간인 `Expires` 또는 `Max-Age` 파라미터가 없다면 세션 쿠기가 된다.

### 쿠키 동작

사용자가 웹 사이트에 방문하면, 웹사이트는 서버가 사용자에게 할당한 값들을 모두 읽을 수 있다.

쿠키는 임의의 이름=값 형태의 리스트를 가지고, `Set-Cookie` or `Set-Cookie2(확장 헤더)`와 같은 HTTP 응답 헤더에 기술되어

사용자에게 전달 된다.

### 쿠키 상자: 클라이언트 측 상태

쿠키의 기본은 브라우저가 서버 관련 정보를 저장하고, 사용자가 해당 서버에 접근할 때 마다

그 정보를 함께 전송하게 하는 것

브라우저가 쿠키 정보를 저장할 책임이 있는데 이게 클라이언트 측 상태 = `HTTP 상태 관리 체계`

#### 구글 크롬 쿠키

<img width="1226" alt="스크린샷 2022-01-13 오후 10 59 56" src="https://user-images.githubusercontent.com/74235102/149343821-0261788e-579d-4551-9965-bfc04e3f5625.png">

### 각 사이트마다 다른 쿠키

브라우저는 수백 수천 개의 쿠키를 가지고 있을 수는 있지만,

브라우저가 쿠키 전부를 모든 사이트에 보내지는 않는다.

1. 모두 전달하면 성능이 크케 저하
2. 특정 서버에 특화된 이름=값 쌍을 포함하기 때문에 인식하지 않는 무의미한 값 존재
3. 특정 사이트에서 제공된 정보를 다른 사이트에서 가져갈 수 있어 개인적인 정보 문제 존재

#### 쿠키 도메인 속성

`Set-cookie: user="lsj8367"; domain="sprout.or.kr"`

`sprout.or.kr` 도메인에 `user="lsj8367"`을 전달한다라는 의미

#### 쿠키 Path 속성

Path 속성으로 해당 경로쪽에 속하는 페이지만 쿠키를 전달하게 하는 속성

## Version 0 쿠키

넷스케이프 쿠키

Set-Cookie 속성

- 이름=값
- **필수 속성**
- 이름, 값 둘다 큰따옴표로 감싸지 않은, `;`, `,`, `=`, 공백 을 포함하지 않는 문자열
- Expires
- 선택적 속성
- 쿠키의 생명주기를 가리키는 날짜 문자열
- 이 일자에 다다르면 그 쿠키는 삭제됨
- 사용할 수 있는 타임 존 = GMT
- 형식 : `요일, DD-MM-YY HH:MM:SS GMT`
- 명시하지 않으면 사용자 세션이 끝날 때 파기
- Domain
- 선택적 속성
- 속성에 기술된 도메인을 사용하는 호스트만 쿠키를 전송
- 명시되어 있지 않다면, `Set-Cookie` 응답을 생성한 서버의 호스트명을 사용
- Path
- 선택적 속성
- 서버에 있는 특정 URL만 쿠키 할당
- 경로를 명시하지 않으면, `Set-Cookie` 응답을 전달하는 URL의 경로 사용
- Secure
- HTTP가 SSL 보안 연결을 사용할 때만 쿠키 전송

## Version 1 쿠키

Set-Cookie2 속성

- 이름=값
- **필수 속성**
- `$` 는 예약된 문자이므로 쿠키 이름은 `$`로 시작하면 안된다.
- Version
- **필수 속성**
- 쿠키 명세 버전을 가리키는 정수 값
- Comment
- 선택적 속성
- 서버가 쿠키를 사용하려는 의도 기술
- 인코딩 반드시 UTF-8
- CommentURL
- 선택적 속성
- 쿠키 사용 목적과 정책에 대해 상세하게 기술된 URL 링크 제공
- Discard
- 선택적 속성
- 이 속성이 있다면, 클라이언트 프로그램 종료될 때 클라이언트가 해당 쿠키를 삭제
- Domain
- 선택적 속성
- 기술된 도메인에 해당하는 서버 호스트들에게만 쿠키 전송
- Max-Age
- 선택적 속성
- 쿠키 생명주기 `초 단위`
- 클라이언트는 HTTP/1.1 수명 계산 규칙에 따라 수명을 계산해야 한다
- Path
- 선택적 속성
- 서버에 있는 특정 문서에만 쿠키 할당
- Version 0 과 동일
- Port
- 선택적 속성
- 값 없이 속성의 키워드만 기술할 수 있고, 포트를 한개 이상 콤마를 이용하여 구분 기술할 수 있음
- Secure
- HTTP가 SSL 보안 연결을 사용할 때만 쿠키가 전송

## 쿠키와 세션 추적

쿠키는 웹 사이트에 수차례 트랜잭션을 만들어내는 사용자를 추적하는데 사용한다.

## 쿠키와 캐싱

쿠키 트랜잭션과 관련된 문서를 캐싱하는 것을 주의해야 한다.

> 이유?

이전 사용자의 쿠키가 다른 사용자에게 할당되거나, 개인정보까지도 노출이 될 수 있다.

### 캐시를 다루는 기본 원칙

**캐시되지 말아야 할 문서가 있다면 표시해라**

문서가 `Set-Cookie` 헤더를 제외하고 캐시해도 되는 경우라면,

그 문서에 명시적으로 `Cache-Control: no-cache="Set-Cookie"` 를 기술하여 명확히 표시한다.

캐시를 해도 되는 문서에 `Cache-Control: public` 을 사용하면 웹의 대역폭을 더 절약시켜준다.

**Set-Cookie 헤더를 캐시 하는 것에 유의하라**

같은 `Set-Cookie` 헤더를 여러 사용자에게 보내게 되면, 사용자 추적에 실패할 것

어떤 캐시는 `Set-Cookie` 헤더를 응답 저장전에 제거한다.

이런 문제를 방지하기 위해서 모든 요청마다 캐시가 원 서버와 재검사를 시켜 `Set-Cookie` 헤더 값을 주어

이 문제를 개선할 수 있다.

`Cache-Control: must-revalidate, max-age=0`

**Cookie 헤더를 가지고 있는 요청을 주의하라**

요청에 Cookie 헤더와 같이 넘겨지면, 결과가 개인정보를 담고 있을 수 있다.

## 쿠키, 보안 그리고 개인정보

쿠키를 사용하지 않도록 비활성화도 가능하기 때문에, 이 자체가 보안상으로 위험하다고 할수는 없다.

개인정보를 다루거나 사용자를 추적하는 기술은 잘못된 의도로 사용할 수 있기 때문에 주의하여야 한다.

**가장 큰 오용중 하나는 사용자 추적을 위한 지속 쿠키**이다.

개인 정보를 누가 받는지 명확하게 파악하고, 개인정보 정책을 유의한다면

위험성 보다는 세션 조작이나 트랜잭션상 편리함이 크다.

728x90

+ Recent posts