캐시

캐시가 없다면 데이터가 변경되지 않더라도 매번 네트워크를 통해서 데이터를 다운로드 받아야 한다.

하지만 네트워크는 매우 느리고 비싸기 때문에 브라우저 로딩 속도에 영향을 준다. 이러한 이유로 캐시를 사용하는 것이 좋다.

 

헤더 종류

캐시 제어 헤더

  • Cache-Control: 캐시 제어
    • 오리진 서버뿐만 아니라 프록시 캐시 서버도 제어할 수 있다.
  • Pragma: 캐시 제어(하위 호환)
  • Expires: 캐시 유효 기간(하위 호환)

검증 헤더

  • 캐시 유효시간이 초과하더라도, 캐시 데이터와 서버 데이터가 동일한지 검증하는 용도
    • 동일한 경우 메세지 바디 없이 304로 응답
  • 응답 시 사용
  • 종류
    • ETag : 캐시용 데이터에 임의의 고유한 버전 이름을 달아둠. ex) ETag: "v1.0"
    • Last-Modified : 마지막으로 수정된 날짜와 시간이 명시된다. ex) 2023년 4월 910:00:00

조건부 요청 헤더

  • 조건을 만족하면 200 OK, 만족하지 않으면 304 Not Modified를 반환
  • 요청 시 사용
  • 종류
    • If-Match, If-None-Match: ETag 값 사용
    • If-Modified-Since, If-Unmodified-Since: Last-Modified 값 사용

 

ETag vs Last Modified

날짜 기반의 Last Modified는 다음의 단점을 가지고 있다.

1. 1초 미만 단위로 캐시 조정이 불가능

2. 날짜 기반의 로직이 사용되며, 서버가 별도의 캐시 로직을 관리하지 않음.

  • 수정된 결과가 이전 버전과 동일하더라도, 서로 다른 데이터로 인식된다. 이때 서버는 캐시 로직을 관리하지 않기 때문에 아무것도 할 수 없다.

 

이와 달리 ETag는 캐시 제어 로직을 서버 측에서 관리할 수 있다.

 

캐시 무효화

확실하게 캐시를 무효화하기 위해서는 다음과 같이 작성한다.

Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
  • Cache-Control: no-cache
    • 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용
    • 원 서버 접근 실패 시 설정에 따라서 캐시 데이터를 반환할 수 있음.(Error or 200 OK)
  • Cache-Control: no-store
    • 데이터에 민감한 정보가 있으므로 저장 X(메모리에서 사용하고 최대한 빨리 삭제)
  • Cache-Control: must-revalidate
    • 캐시 만료후 최초 조회 시 원 서버에 검증해야 함
      • 캐시 유효시간이라면 캐시 사용
    • 원 서버 접근 실패 시 반드시 오류가 발생해야 함(504 Gateway Timeout)
  • Pragma: no-cache
    • HTTP 1.0 하위 호환

'개발 도서 및 강의 > 모든 개발자를 위한 HTTP 웹 기본 지식' 카테고리의 다른 글

7. HTTP - 일반 헤더  (0) 2023.04.09
6. HTTP 상태 코드  (0) 2023.04.02
5. HTTP 메서드 활용  (0) 2023.04.02
3. HTTP 기본  (0) 2023.04.02
4. HTTP 메서드  (0) 2023.03.29

표현

  • 표현은 요청이나 응답에서 전달할 실제 데이터를 의미한다. 
    • 데이터는 html이나 json 등의 형태로 표현될 수 있기 때문에 표현이라는 이름으로 지어졌다.
  • 표현은 표현 메타데이터와 표현 데이터로 구성된다.
    • 표현 메타데이터 : 표현 데이터를 해석할 수 있는 정보 제공

 

HTTP 헤더

  • HTTP 전송에 필요한 모든 부가정보를 의미한다.
  • field-name : field-value 형태를 가진다.
  • 다양한 표준 헤더가 존재하며, 필요시 임의의 헤더 추가가 가능하다.

표현 헤더 - 요청, 응답 둘 다 사용

  • Content-Type : 표현 데이터의 형식 
    • ex) text/html: charset=utf-8, application/json 등
  • Content-Encoding: 표현 데이터의 압축 방식
    • 데이터를 전달하는 곳에서 압축 후 인코딩 헤더 추가
    • ex) gzip, deflate, identy
  • Content-Language: 표현 데이터의 자연 언어
    • ex) ko, en 등
  • Content-Length: 표현 데이터의 길이
    • 바이트 단위

협상 - 요청만 사용

  • Accept: 클라이언트가 선호하는 미디어 타입 전달
  • Accept-Charset: 클라이언트가 선호하는 문자 인코딩
  • Accept-Encoding: 클라이언트가 선호하는 압축 인코딩
  • Accept-Language: 클라이언트가 선호하는 자연 언어

선호하는 값이 여러 개인 경우, 다음의 규칙에 따라 우선순위가 결정된다.

  1. Quality Values(q)가 클수록 높은 우선순위를 가진다.(기본값 1)
  2. 구체적인 것이 우선한다.
    • ex) Accept: text/*, text/plain -> text/plain > text/*

전송 방식에 따른 구분

  1. 단순 전송 -> Content-Length만 사용하면 된다.
  2. 압축 전송 -> 압축 후 Content-Encoding을 추가한다.
  3. 분할 전송 -> Transfer-Encoding을 사용한다. 바디에 길이가 명시되므로, Content-Length는 사용하지 않는다.
  4. 범위 전송 
    • 요청 시 : Range: bytes=1001-2000과 같은 형태로 요청한다.
    • 응답 시 : Content-Range: bytes 1001-2000 / 2000과 같은 형태로 응답한다.

일반 정보

  • From: 유저 에이전트의 이메일 정보
    • 검색 엔진과 같은 곳에서 주로 사용하며, 그 외에는 잘 사용하지 않는다.
  • Referer: 이전 웹 페이지 주소
    • 유입 경로 분석을 가능하게 한다.
  • User-Agent: 유저 에이전트 애플리케이션 정보
    • 클라이언트의 애플리케이션 정보
    • 어떤 종류의 브라우저에서 장애가 발생했는지 파악 가능
  • Server: 요청을 처리하는 오리진 서버의 소프트웨어 정보
    • 프록시가 아닌, 실제 요청을 처리하는 서버를 의미
  • Date: 메시지가 생성된 날짜와 시간

특별한 정보

  • Host: 요청한 호스트 정보(도메인)
    • 하나의 IP 주소에 여러 도메인이 적용되어 있을 때(가상 호스트)가 있으므로, 필수이다.
    • ex) Host: www.google.com
  • Location: 요청에 의해 생성된 리소스 URI를 명시(201)하거나, 사용자 요청을 리다이렉션(3xx)하기 위해 사용한다.
  • Allow: 허용 가능한 HTTP 메서드
    • 405(Method Not Allowed) 응답 시 포함한다.
  • Retry-After: 유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간
    • 서비스가 언제까지 사용 불가능한지 알려줄 수 있다.(503, Service Unavailable)

인증

  • Authorization: 클라이언트 인증 정보(JWT 토큰 등)를 서버에 전달
  • WWW-Authenticate: 리소스 접근시 필요한 인증 방법 정의
    • 401 Unauthorized 응답과 함께 사용

쿠키

  • Set-Cookie: 서버에서 클라이언트로 쿠키 전달(응답)
    • 생명 주기, 적용 도메인, 경로, 보안 관련한 정보를 지정해서 넘길 수 있다.
  • Cookie: 클라이언트가 서버에서 받은 쿠키를 저장하고, HTTP 요청 시 서버로 전달

 

참고하면 좋을 자료

- How do we control web page caching, across all browsers?

'개발 도서 및 강의 > 모든 개발자를 위한 HTTP 웹 기본 지식' 카테고리의 다른 글

8. HTTP 헤더 - 캐시와 조건부 요청  (0) 2023.04.09
6. HTTP 상태 코드  (0) 2023.04.02
5. HTTP 메서드 활용  (0) 2023.04.02
3. HTTP 기본  (0) 2023.04.02
4. HTTP 메서드  (0) 2023.03.29

HTTP 상태 코드

  • 클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주는 기능
  • 클라이언트가 인식할 수 없는 상태 코드를 서버가 반환하면, 상위 상태 코드로 해석해서 처리한다.
    • ex) 299 -> 2xx, 451 -> 4xx
  • 종류
    • 1xx(information) : 서버가 요청을 클라이언트에서 성공적으로 수신을 했고, 처리 중인 정보를 보낸다.
    • 2xx(Successful) : 서버가 요청을 정상 처리했음을 알린다.
    • 3xx(Redirection) : 요청을 완료하려면 추가 행동이 필요하다.
    • 4xx(Client Error) : 클라이언트 오류, 잘못된 문법 등으로 서버가 요청을 수행할 수 없다.
    • 5xx(Server Error) : 서버 오류, 서버가 클라이언트의 요청을 처리하지 못했다.

1xx(information)

  • 거의 사용하지 않는다.

2xx(Successful)

code reason phrase 설명
200 OK 성공
201 Created 요청을 처리해서 새로운 리소스가 생성됨
202 Accepted 요청이 접수되었으나, 처리가 완료되지 않음
204 No Content 서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없음

3xx(Redirection)

  • 웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, location 위치로 자동 이동한다.(리다이렉트)
code reason phrase 설명
300 Multiple Choices 요청에 대해서 하나 이상의 응답이 가능하며, 클라이언트는 그 중에 하나를 반드시 선택해야 한다.
301 Moved Permanently 리다이렉트 시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있음(MAY)
302 Found 리다이렉트 시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있음(MAY)
303 See Other 리다이렉트 시 요청 메서드가 GET으로 변경
304 Not Modified 클라이언트에게 리소스가 수정되지 않았음을 알려준다. 따라서 클라이언트는 로컬 PC에 저장된 캐시를 재사용한다.
307 Temporary Redirect 리다이렉스 시 요청 메서드와 본문  유지(요청 메서드를 변경하면 안된다. MUST NOT)
308 Permanent Redirect 리다이렉스 시 요청 메서드와 본문 유지(처음 POST 요청 시 리다이렉트도 POST 유지)

종류

  1. 영구 리다이렉션
    • 리소스의 URI가 영구적으로 이동
    • 원래의 URL을 사용하지 않기 때문에 검색 엔진 등에서 변경을 인지할 수 있다.
    • 301, 308
  2. 일시 리다이렉션 : 일시적인 변경
    • 리소스의 URI가 일시적으로 변경
    • PRG 시 사용
    • 302, 307, 303
      • 307, 303을 권장하지만, 302가 이미 상용화되었다.
  3. 특수 리다이렉션
    • 클라이언트에게 리소스가 수정되지 않았음을 알려준다. 따라서 캐시를 재사용한다.
    • 응답 시 메시지 바디를 포함하면 안된다.(로컬 캐시를 사용하기 때문)
    • 조건부 GET, HEAD 요청 시 사용
    • 304

PRG

  • Post / Redirect / Get
  • Post 요청 후 새로 고침으로 인한 중복 처리를 방지하기 위해 GET 메서드로 리다이렉트를 한다.
    • 새로고침 해도 결과 화면을 GET으로 조회

4xx(Client Error)

  • 오류의 원인이 클라이언트에게 있기 때문에 똑같은 재시도를 하더라도 실패한다.
code reason phrase 설명
400 Bad Request 클라이언트가 잘못된 요청을 해서 서버가 요청을 처리할 수 없음. 요청 파라미터, API 스펙 미일치 등
401 Unauthorized 클라이언트가 해당 리소스에 대한 인증이 필요함
403 Forbidden 서버가 요청을 이해했지만, 승인을 거부함
404 Not Found 요청 리소스를 찾을 수 없음. 권한이 부족한 리소스에 접근할 때 해당 리소스를 숨기는 용도로도 사용

5xx(Server Error)

code reason phrase 설명
500 Internal Server Error 서버 문제로 오류 발생. 애매하면 500 오류
503 Service Unavailable 일시적인 과부하, 예정된 작업 등으로 인한 서비스 이용 불가

클라이언트의 요청 방식

1. 데이터 전달 방식

1) 쿼리 파라미터를 통한 데이터 전송(GET)

2) 메시지 바디를 통한 데이터 전송(POST, PUT, PATCH)

 

2. 상황에 따른 구분

1) 정적 데이터 조회

  • 일반적으로 쿼리 파라미터 없이 리소스 경로로 단순하게 조회
    • ex) 이미지, 정적 테스트 문서
  • GET 사용

2) 동적 데이터 조회

  • 쿼리 파라미터를 사용해서 데이터 전달
    • ex) 조회 조건을 줄여주는 필터, 검색 등
  • GET 사용
  • 주로 필터나 정렬 조건으로 사용

3) HTML Form 데이터 전송

  • HTML Form submit 시 데이터 전송
  • GET, POST만 지원
  • POST Content-Type 종류
    • application/x-www-form-urlencoded
      • form의 내용을 메시지 바디를 통해서 전송(key=value, 쿼리 파라미터 형식)
      • 전송 데이터를 url encoding 처리
    • multipart/form-data
      • 파일 업로드 같은 바이너리 데이터 전송 시 사용
      • 여러 종류의 파일을 함께 전송 가능

4) HTTP API 데이터 전송

  • HTTP를 사용해서 서로 정해둔 스펙으로 데이터를 주고받으며 통신하는 것이다.
  • 웹 클라이언트 - 서버, 앱 클라이언트 - 서버, 서버 - 서버에서 사용한다.
    • 웹 클라이언트는 자바스크립트를 통해 통신한다.(AJAX)
  • GET, POST, PUT, PATCH, DELETE 메서드를 모두 지원한다.
  • Content-Type은 주로 application/json을 사용한다.

HTTP API 설계 예시

  • HTTP API - 컬렉션
    • POST 기반 등록
    • 서버가 리소스 URI 결정(컬렉션)
  • HTTP API - 스토어
    • PUT 기반 등록
    • 클라이언트가 리소스 URI 결정(스토어)
  • HTML FORM 사용
    • 순수 HTML + HTML form 사용
    • GET, POST만 사용 가능 -> PUT, PATCH, DELETE 대신 POST + 컨트롤 URI 사용

URI 설계 개념

1. 문서(document)

  • 단일 개념(파일 하나, 객체 인스턴스, 데이터베이스 row)
  • ex) /members/100, /files/star.jpg

2. 컬렉션(collection)

  • 주로 사용
  • 서버가 관리하는 리소스 디렉터리
  • 서버가 리소스의 URI를 생성하고 관리
  • ex) POST /members로 등록 시, 서버가 임의의 위치(ex. /member/100)에 리소스 생성

3. 스토어(store)

  • 파일, 게시판 등에 가끔 사용
  • 클라이언트가 관리하는 자원 저장소
  • 클라이언트가 리소스의 URI를 알고 관리
  • ex) PUT /files/100로 등록 시, 서버는 /files/100 위치에 리소스 생성(또는 대체)

4. 컨트롤러(controller), 컨트롤 URI

  • 문서, 컬렉션, 스토어로 해결하기 어려운 추가 프로세스 실행
    • 즉, 행위와 자원의 조합만으로는 처리하기 어려운 경우
  • URI에 동사를 직접 사용
    • ex) /members/{id}/delete

 

'개발 도서 및 강의 > 모든 개발자를 위한 HTTP 웹 기본 지식' 카테고리의 다른 글

8. HTTP 헤더 - 캐시와 조건부 요청  (0) 2023.04.09
7. HTTP - 일반 헤더  (0) 2023.04.09
6. HTTP 상태 코드  (0) 2023.04.02
3. HTTP 기본  (0) 2023.04.02
4. HTTP 메서드  (0) 2023.03.29

HTTP

  • HyperText Transfer Protocol
  • 인터넷상에서 클라이언트와 서버가 자원을 주고받을 때 사용하는 통신 규약
  • 거의 모든 형태의 데이터 전송이 가능하며, 서버간에 데이터를 주고받을 때도 대부분 HTTP 사용
    • HTML, TEXT
    • IMAGE, 음성, 영상, 파일
    • JSON, XML (API)
  • 주로 TCP 기반의 통신 방식
  • 특징
    • 클라이언트 서버 구조
    • 무상태 프로토콜
    • 비연결성
    • HTTP 메시지
    • 단순하고 확장 가능

1. 기반 프로토콜

  • TCP : HTTP/1.1(가장 많이 사용), HTTP/2
  • UDP : HTTP/3

2. 특징

1) 클라이언트 서버 구조

  • Request Response 구조
  • 클라이언트는 서버에 요청을 보내고, 서버는 요청에 대한 결과를 만들어서 응답
  • 클라이언트는 UI, 서버는 비즈니스 로직에 집중할 수 있게 되었다.

2) 무상태 프로토콜(stateless)

  • 서버가 클라이언트의 상태를 보존하지 않는다.
    • 중간에 서버가 변경되어도 요청과 응답에 지장이 없다.
  • 모든 것을 무상태로 설계할 수 없는 경우도 있다.
    • ex) 로그인을 하는 경우 로그인한 상태를 서버에서 유지해야한다.
    • 브라우저 쿠키와 서버 세션 등을 사용해서 상태 유지 가능
    • 전송 데이터양이 증가하기 때문에, 상태 유지는 최소한만 사용
  • 장점 : 서버 확장성 높음(스케일 아웃)
  • 단점 : 클라이언트가 추가 데이터 전송

3) 비연결성

  • HTTP는 기본이 연결을 유지하지 않는 모델이다. 즉, 한 번의 요청과 응답이 끝나면 TCP 연결이 종료된다.
  • TCP 기반의 경우 요청할 때마다 연결을 새로 맺어야 한다는 단점이 있지만, HTTP 지속 연결로 이러한 문제를 해소하였다.
    • HTTP/2, HTTP/3에서는 더욱 최적화됨.
  • 비연결성 덕분에 서버 자원을 효율적으로 사용.

4) HTTP 메시지

  1. start-line
    • request-line(요청 메시지의 경우) : method SP request-target SP HTTP-version CRLF
      • HTTP 메서드(ex. GET)
      • 요청 대상(ex. /search?q=hello&hl=ko)
      • HTTP 버전(ex. HTTP/1.1)
    • status-line(응답 메시지의 경우) : HTTP-version SP status-code SP reason-phrase CRLF
      • HTTP 버전
      • HTTP 상태 코드 : 요청 성공, 실패를 나타냄 (ex. 200)
      • 이유 문구 : 사람이 이해할 수 있는 짧은 상태코드 설명 글 (ex. OK)
  2. HTTP 헤더
    • HTTP 전송에 필요한 모든 부가 정보가 포함된다.
      • ex) 메시지 바디의 크기, 압축, 인증, 요청 클라이언트 정보 등
    • 필요시 임의의 헤더 추가 가능
  3. HTTP 메시지 바디
    • 실제 전송할 데이터
    • HTMl 문서, 이미지, 영상, JSON 등 byte로 표현할 수 있는 모든 데이터 전송 가능

5) 단순하고 확장 가능

3. 문제점

  • HTTP는 평문 통신이기 때문에 도청이 가능하다
  • 통신 상대를 확인하지 않기 때문에 위장이 가능하다.
  • 완정성을 증명할 수 없기 때문에 변조가 가능하다.

이러한 문제점을 해결하기 위해 HTTPS가 등장했다.

 

api를 설계할 때 중요한 것은 리소스와 행위를 분리하는 것이다. 따라서 URI는 리소스만 식별해야 하고, 그에 대한 처리는 메서드로 구분한다.

 

주요 HTTP 메서드 종류

1. GET

  • 리소스 조회
  • 서버에 전달하고 싶은 데이터는 쿼리를 통해서 전달.
    • ex) GET /search?q=hello&hl=ko
  • 메시지 바디를 통해 전달 가능. 하지만 지원하지 않는 곳이 많아서 권장하지 않음.

2. POST

  • 요청 데이터 처리
    • 주로 신규 리소스 등록, 프로세스 처리에 사용.
    • 응답값이 없기도 함.
    • 포괄적으로 정의되어 있기 때문에, 다른 메서드를 사용하기 애매하다 싶으면 사용해도 됨.
    • PATCH, PUT을 지원하지 않는 경우에도 사용 가능.
  • GET과 달리, 메시지 바디를 통해 요청 데이터 전달

3. PUT

  • 리소스가 있으면 대체, 리소스가 없으면 생성.
    • 요청 시 필드를 누락하면, null로 저장되는 것을 주의.
  • 클라이언트가 리소스 위치를 알고 URI를 지정
    • ex) PUT /members/100

4. PATCH

  • 리소스 부분 변경
  • PUT과 달리, 요청 시 필드를 누락하더라도 해당 필드의 값이 변경되지 않음.

5. DELETE

  • 리소스 제거

 

HTTP 메서드의 속성

1. 안전

  • 몇 번을 호출해도 리소스 변경이 없는 것을 의미한다.
  • GET, HEAD 등

2. 멱등

  • 몇 번을 호출해도 결과가 동일한 것을 의미한다.
    • 안전과 달리 변경이 발생할 수 있다.
  • 멱등 메서드
    • GET : 몇 번을 조회하든 변경이 없고 같은 결과가 조회된다.
    • PUT : 리소스를 대체하므로, 같은 요청을 여러번 해도 결과는 같다.
    • DELETE : 리소스를 삭제하므로, 같은 요청을 여러번 해도 삭제된 상태이다.
    • POST : 멱등이 아니다. 리소스를 등록하는 요청의 경우, 요청할 때마다 새로운 리소스 생성.
    • PATCH : 멱등이 아니다. 나이를 1씩 더하는 요청의 경우, 요청할 때마다 지속적으로 값이 증가.

3. 캐시 가능

  • 응답 결과를 캐시해서 사용해도 되는지에 대한 여부를 의미한다.
  • POST, PATCH도 캐시 가능하지만, 실제로는 GET, HEAD 정도만 사용.
    • GET은 URL만 캐시 키로 고려하면 되지만, POST, PATCH는 본문 내용까지 고려해야 하기 때문에 구현이 쉽지 않음.

 

1. 애그리거트 트랜잭션

그림 8.1

운영자는 고객의 배송 상태를 배송 시작으로 변경하고, 고객은 배송지를 변경하는 것이 동시에 발생하는 상황이다. 이때 요구 사항이 `배송이 시작되면 배송지 변경 불가`라면, 애그리거트의 일관성이 깨질 수가 있다.

 

이러한 문제를 해결하기 위해서는 트랜잭션 처리가 적용되어야 한다. 대표적인 처리 방식으로는 비관적 락(선점 잠금)과 낙관적 락(비선점 잠금)이 있다.

 

2. 비관적 락

그림 8.2

스레드 2는 스레드 1이 잠금을 해제한 뒤에 애그리거트에 접근할 수 있다. 이처럼 한 스레드의 애그리거트 사용이 끝날 때까지 다른 스레드의 접근을 막는 방식을, 비관적 락이라고 한다.

보통 DBMS의 for update와 같은 쿼리를 사용해서 행단위 잠금을 건다. JPA를 사용하면 보다 쉽게 구현할 수 있다.

 

애그리거트를 동시에 수정할 수 없도록 막기 때문에(상호 배제) 갱신 손실 문제가 발생하지 않는다. 다만 순환 대기, 비선점, 점유 대기를 만족하는 경우에는 데드락이 발생할 수 있는 점을 주의해야 한다.

이에 대한 해결 방법에는 최대 자원 대기 시간을 설정하는 방법이 있다. 이는 힌트를 사용하여 쉽게 구현할 수 있다. 그런데  DBMS에 따라 1) 쿼리별로 대기 시간을 지정하거나 2) 커넥션 단위로만 지정하는 경우로 나뉘니, 사전에 꼭 확인하자

 

3. 낙관적 락

그림 8.3

그림 8.2에서는 운영자와 고객이 변경을 동시에 수행하는 상황이었다. 하지만 그림 8.3에서는 운영자가 사전에 조회한 정보를 바탕으로 변경이 따로 발생하는 상황이다. 이는 비관적 락으로 해결할 수 없지만, 낙관적 락으로는 해결할 수 있다.

 

낙관적 락은 동시에 접근하는 것을 막는 방식이 아니라, 변경한 데이터를 실제 DBMS에 반영하는 시점메 변경 가능 여부를 확인하는 방식이다. 변경 가능 여부는 버전 관리 방식을 통해 구현할 수 있다.

 

그림 8.4

좀 더 원리를 살펴보자면, 사전에 조회한 애그리거트의 버전 값이 새로 조회한 버전 값과 같은 경우에만 데이터를 수정할 수 있다. 그리고 수정에 성공하면 버전 값을 1 증가시키는 방식이다.

 

강제 버전 증가

애그리거트는 여러 엔티티와 밸류로 구성되어 있다. 이때 루트 엔티티가 아닌 다른 엔티티의 값이 변경되더라도, 애그리거트 관점에서는 버전이 달라져야 하는 게 올바르다. 이 역시 JPA는 관련 기능을 지원한다.

 

4. 오프라인 비관적 락

사실 비관적 락을 사용해서 그림 8.3을 해결하는 방법이 있다.

그림 8.5

첫 번째 트랜잭션에서 오프라인 락을 걸고 마지막 트랜잭션에서 락을 해제하면 동시성 문제를 해소할 수 있다. 이러한 방식을 오프라인 비관적 락이라고 부른다.

 

락을 걸 때는 애그리거트에 lockId를 저장하고, 이후에 lockId를 사용하여 락을 해제하는 방식으로 구현할 수 있다. 사용자가 락을 걸기만 하고 해제하지 않을 수도 있으므로, 잠금 유효 시간을 가져야 함을 유의하자.

 

5. 비관적 락과 낙관적 락 

그러면 비관적 락과 낙관적 락을 언제 사용해야 할까? 결론부터 말하자면 낙관적 락은 많은 충돌이 예상되지 않을 때(낙관적인 상황) 사용하고, 비관적 락은 잦은 충돌이 예상될 때(비관적인 상황) 사용하면 된다.

 

갱신 충돌이 자주 발생하지 않는 경우에는 낙관적 락이 성능이 더 좋다. 비관적 락은 다른 트랜잭션이 접근하지 못하도록 락을 걸기 때문에 동시성이 떨어지기 때문이다. 하지만 반대되는 상황에서는 그렇지 않다.

충돌이 잦을수록 트랜잭션이 중단되어 롤백할 가능성이 높아진다. 하지만 롤백은 보류 중인 모든 변경 사항을 되돌려야 하므로 비용이 많이 드는 작업이다.

 

이러한 이유로 충돌이 자주 발생하는 경우에는 비관적 락이 더 적합할 수 있다.

 

참고자료

https://stackoverflow.com/questions/129329/optimistic-vs-pessimistic-locking

1. 여러 애그리거트가 필요한 기능

도메인 영역의 코드를 작성하다 보면, 한 애그리거트로 기능을 구현할 수 없을 때가 있다. 대표적인 예가 상품, 주문, 할인 쿠폰, 회원 애그리거트들이 관여하는 결제 금액 계산 로직이다.

 

이 상황에서는 어떤 애그리거트가 주체인지 쉽게 판단할 수 없다. 어찌어찌 한 도메인 안에 욱여 넣더라도, 자신의 책임 범위를 넘어서는 기능을 구현하기 때문에 다음의 단점이 존재한다. 1) 코드가 길어지고 2) 외부에 대한 의존이 높아지게 되며 3) 코드가 복잡하여 수정이 어렵다. 4) 게다가 애그리거트 범위를 넘어서는 도메인 개념이 애그리거트에 숨어들어 명시적으로 드러나지 않게 된다.

 

이에 대한 해결 방법으로는 별도 서비스로 구현하는 것이다.

 

2. 도메인 서비스

도메인 서비스는 도메인 영역에 위치한 도메인 로직을 표현할 때 사용한다. 주로 여러 애그리거트가 필요한 계산이나 외부 시스템 연동이 필요한 경우에 사용한다.

 

계산 로직과 도메인 서비스

결제 금액 계산처럼 한 애그리거트에 넣기 애매한 도메인 기능은, 도메인 서비스를 이용해서 도메인 개념을 명시적으로 드러낼 수 있다.

 

응용 서비스 vs 도메인 서비스

응용 서비스는 도메인 로직 없이, 그저 표현 영역과 도메인 영역을 연결하는 창구 역할을 수행한다. 따라서 응용 서비스는 애그리거트나 도메인 서비스의 로직을 실행하기만 할 뿐이다.

반면 도메인 서비스는 애그리거트의 상태를 변경하거나 상태 값을 계산하는 도메인 로직이 존재한다. 트랜잭션 처리와 같은 로직은 응용 로직이므로 응용 서비스에서 처리한다.

 

도메인 서비스 vs 애그리거트가 가진 기능들

애그리거트는 도메인 로직 뿐만 아니라 관련된 필드를 가지고 있다. 반면 도메인 서비스는 상태를 가지지 않고 기능만 가진다. 애그리거트의 상태값이 필요하다면, 응용 서비스의 도움을 받아 메서드 파라미터를 통해 주입받을 수 있다.

 

외부 시스템 연동과 도메인 서비스

전에 우리는 JPA 레포지터리 인터페이스는 도메인 영역에 포함시키고, 구현 클래스는 인프라 영역에 포함시켰다. 또한, 도메인 로직 관점에서 인터페이스를 작성했었다. 이와 비슷하다.

 

외부 시스템이나 타 도메인과의 연동은 도메인 서비스 포함시킬 수 있다. 가령 구현 부분이 HTTP 호출로 이루어져 있더라도 상관없다. 도메인 로직 관점에서 인터페이스를 작성한 뒤 도메인 서비스로 포함시키고, 구현 클래스를 인프라 영역으로 보내면 그만이다.

1. 표현 영역과 응용 영역

앞서 우리는 소프트웨어로 해결하고자 하는 영역이 도메인이라는 점을 학습하였다. 그러나 이것만으로 끝나는 것이 아니다. 사용자와 도메인 영역을 연결해 주는 매개체인 표현 영역과 응용 영역이 필요하다.

 

2. 응용 영역(응용 서비스)

1. 역할

응용 서비스의 역할은 다음과 같다.

1. 사용자(클라이언트)가 요청한 기능을 실행한다.

 코드의 응집성을 높이고 코드 중복을 제거하기 위해, 도메인 로직 없이 그저 도메인의 기능을 실행하는 역할만 한다.

2. 트랜잭션 처리를 담당한다.

 기능 실행 도중에 문제가 발생하면 초기 상태로 되돌아 갈 수 있어야 한다.

 

다만 조회 기능만 필요한 경우라면, 응용 서비스 없이 구현하는 것을 고려해도 좋다.

2. 크기

응용 서비스의 크기를 어떻게 하느냐에 따라 구현 방식이 1) `한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기` 2) `구분되는 기능별로 응용 서비스 클래스를 따로 구현하기`로 나뉜다.

 

방식 1) 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기

장점 : 도메인에 관련된 기능을 구현한 코드가 한 클래스에 위치하므로, 각 기능에서 동일 로직에 대한 코드 중복을 쉽게 제거할 수 있다.

  ex) 여러 메서드에 있는 null값 검사 기능을 별도의 메서드로 추출

단점 : 한 서비스 클래스의 크기(코드 줄 수)가 커진다. 그로 인해 관련 없는 코드가 추가되면서 코드의 품질이 저하된다.

 

방식 2)  구분되는 기능별로 응용 서비스 클래스를 따로 구현하기 -> 글쓴이가 선호하는 방식

장점 : 코드의 품질을 유지할 수 있고, 클래스별로 필요한 의존 객체만 보유할 수 있다.

단점 : 클래스의 개수가 많아진다.

 

3. 인터페이스와 클래스

인터페이스와 클래스를 따로 구현하면 1) 소스 파일이 많아지고 2) 구현 클래스에 대한 간첩 참조가 증가해서 전체 구조가 복잡해진다. 따라

서 필요한 상황(ex. 구현 클래스가 여러 개인 경우)에만 만드는 것을 고려하자.

 

4. 메서드 파라미터

응용 서비스는 필요한 값을 개별 파라미터나 별도의 dto로 전달받을 수 있다. 보통 요청 파라미터가 두 개 이상이면 dto를 사용하는 것이 편리하다.

여기서 주의할 점은 HttpServleRequest와 같이 표현 영역과 관련된 타입을 사용하지 않는 것이다. 표현 영역에 의존이 발생하면 1) 응용 서비스만 단독으로 테스트하기 어렵고 2) 표현 영역이 변경되면 응용 서비스 구현이 변경될 수 있다. 3) 응용 서비스가 표현 영역의 역할까지 대신한다는 단점도 존재한다.

 

5. 값 리턴

응용 서비스에서 애그리거트 자체를 리턴하면 코딩은 편할 수 있다. 하지만 도메인 로직 실행을 응용 서비스와 표현 영역 두 곳에서 할 수 있게 된다. 이는 곧, 기능 실행 로직이 두 영역에 분산되기 때문에 코드의 응집도를 낮추는 원인이 된다. 추가로 JPA에서 OSIV 설정을 false로 하는 경우, 지연 로딩이 서비스 단까지만 가능하기 때문에 더더욱 애그리거트를 표현 영역에 리턴하지 말자.

 

4. 표현 영역

표현 영역의 책임은 크게 다음과 같다.

1) 사용자가 시스템을 사용할 수 있는 화면을 제공하고 제어한다.

2) 사용자의 요청을 응용 서비스에 전달하고, 그 결과를 사용자에게 제공한다.

3) 사용자의 세션을 관리한다.

 

5. 값 검증

원칙적으로는 모든 값에 대한 검증은 응용 서비스에서 처리한다. 다만 구현의 편리함을 위해 다음처럼 구현하는 것을 고려해도 좋다.

- 표현 영역 : 필수 값, 값의 형식, 범위 등을 검증한다. -> 필드 에러 검증

- 응용 서비스 : 데이터의 존재 유무와 같은 논리적 오류를 검증한다. -> 글로벌 에러 검증

 

글쓴이는 코드 작성의 불편함보다는 응용 서비스의 완성도가 높아지는 이점이 더 크다고 하였다. 하지만 그 이점이 무엇인지는 서술하지 않았기 때문에 이 부분은 찾아봐야겠다.

해당 챕터는 JPA에 대한 내용이 주를 이루기 때문에 간략하게 정리하였다.
5장은 Specification에 대해 다루는데, QueryDSL이 더 좋은 대안이라고 생각해서 가볍게 훑고 넘어갔다.

1. JPA를 이용한 리포지터리 구현

2장에서 언급한 것처럼 리포지터리 인터페이스는 도메인 영역에 속하고, 구현 클래스는 인프라스트럭처 영역에 속한다. 

 

TMI 삭제기능

삭제 요구사항이 있더라도 데이터를 실제로 삭제하는 경우는 많지 않다. 관리자 기능에서 삭제 데이터를 조회하는 경우도 있고 원복을 위해 일정 기간 동안 보관하는 경우도 있기 때문이다. 따라서 데이터를 바로 삭제하기보다는, 삭제 플래그를 사용하는 방식으로 구현하자.

 

2. 매핑 구현

객체는 여러개지만 테이블은 하나이다.

주문 애그리거트는 위와 같이 여러 개의 객체로 구성되지만 테이블은 하나이다. 구현 방법은 아래처럼 @Embeddable@Embedded 애노테이션을 사용하면 된다.

@Entity
pulic class Order {

  @Embedded
  private Orderer orderer;
  
  @Embedded
  private ShippingInfo shippingInfo;
  ...
}

@Embeddable
public class ShippingInfo {
  
  @Embedded
  private Address address;
  
  @Embedded
  private Receiver receiver;
  
  protected ShippingInfo() {} // JPA에서는 private이 아닌 기본 생성자가 있어야 한다.
  
  public ShippingInfo(Address address, Receiver receiver) {
    this.address = address;
    this.receiver = receiver;
  }
  ...
}

 

3. 별도 테이블에 저장하는 밸류 매핑

보통 애그리거트에서 루트 엔티티를 뺀 나머지 구성요소는 대부분 밸류이다. 루트 엔티티 외에 다른 엔티티가 있다면 진짜 엔티티인지 의심하라. 그저 별도 테이블에 저장되어 있는 밸류일 수도 있다. 또는 다른 애그리거트일 수도 있으니 유념하자.

 

4. 도메인 구현과 DIP

@Entity, @Table은 구현 기술에 속한다. 하지만 해당 애노테이션을 가지는 엔티티는 도메인에 속한다. 즉, 도메인이 인프라에 의존하기 때문에 DIP 위반이다.

 

DIP를 적용하는 주된 이유는 저수준 구현이 변경되더라도 고수준이 영향을 받지 않도록 하기 위함이다. 하지만 필자는 다음의 이유로 타협해도 된다고 판단하였다.

1. 리포지터리와 도메인 모델의 구현 기술은 거의 바뀌지 않는다. 변경이 거의 없는 상황에서 변경을 대비하는 것은 과하다고 생각한다.

2. JPA 전용 애노테이션을 사용했지만 도메인 모델을 단위 테스트 하는 데 문제없다. 리포지터리 역시 마찬가지다.

 

+ Recent posts