courses 하위에 lecture와 application이 있다.
여기서는 로그인 URI를 /login이라고 표기했다.
다른 방식으로도 쓸 수 있는데 로그인을 하면 세션을 획득한다고 해서 /sessions 로 쓰이기도 한다.
그래서 로그인 Method를 POST로 하고 URI를 /sessions라 한 것.
로그아웃은 해당 세션이 삭제되는 거니까 DELETE, /sessions라고 표현.
회원가입은 POST, /users로 쓰일수 있다. 하지만 이렇게 쓴다면 관리자가 유저생성을 하게 될 때 일반 회원의 회원가입과 관리자가 같은 /users를 사용하게 되어 유저 생성을 하는 것이 된다.
권한이 다른데 같은 방식으로 유저를 생성하게 되어 겹치기 때문에 이렇게 쓰면 안된다.
그래서 간단하게 일반 회원이 이용하는 것을 목적으로 /signup, /login 으로 표현했다.
프로필 수정 URI에서는 /users/{userId}/profile로 표기.
유저정보인 이메일, 비밀번호가 수정이 되면 안되기 때문에 수정가능한 정보들을 profile로 새로 정의한 것. 지금은 닉네임밖에 없지만 나중에는 성별, 나이, 핸드폰 번호가 들어가며 profile이 커질 수 있다.
어플리케이션에 종속적이지 않기 때문에 새로 패키지를 생성.
빈을 등록하는 방법 2가지(@Component, @Configuration + @Bean) 중
후자 이용.
Service Layer, Repository Layer가 아니기 때문에 이와 같이 어플리케이션에서 전역적으로 사용해야 하는 설정들(클래스)은 Configuration을 통해서 스프링 지정 파일로 등록.
제일 먼저 읽히는 부분. 스프링 프레임워크가 빈을 등록할 때 이 부분을 제일 먼저 읽는다.
import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class SwaggerConfig {
@Bean
fun openAPI(): OpenAPI = OpenAPI()
.components(Components()) //기본값
.info(
Info()
.title("Course API")
.description("Course API schema") //스키마는 데이터 전체
.version("1.0.0")
)
}
이렇게 하면 문서화 작성 완료.
스키마 : ‘데이터의 구조’ 또는 ‘데이터베이스의 설계’ 를 의미한다. 데이터베이스 스키마는 논리적 보기를 나타내는 일종의 골격구조와 같다. 데이터베이스의 데이터에 적용되는 모든 제약 조건을 이 골격구조로 고안해낸다. 스키마는 데이터베이스를 구성하는 데이터 개체(entity), 속성(Attribute), 관계(Relationship) 및 데이터 조작시 데이터 값들이 갖는 제약 조건 등에 관해 전반적으로 정의한다. 정리하면, 사용자들이 즉 검색을 하는 사람(=결과값을 기다리는 사람=뷰어)들이 여러가지 각각의 궁금증을 가지고 검색을 하게 되었을 때 맨 아래 저장되어 있는 데이터베이스까지 닿아 정보를 얻기까지의 구조, 그 일련의 데이터 처리 프로세스, 자료들간의 전반적인 협력관계 등이 아주 긴밀하게 움직이게 되는데 이를 ‘형식 언어’로 나타낸 데이터베이스의 구조를 뜻 한다. 이를 데이터베이스의 구조와 제약조건에 관한 전반적인 명세를 기술한 메타데이터의 집합이다.
빈을 통해 openAPI 재정의.
타이틀을 바꾸기 위해 빈을 재정의 하는 것.
openAPI는 원래 import 하자마자 자동등록이 된다.
info도 import 하자마자 자동등록이 되는데 타이틀과 디스크립션, 버전 수정을 위해 재정의 한 것.
<기존 페이지>
<빈으로 재정의 한 페이지>
이제 Controller를 통해서 API 작성을 하면 자동으로 문서화가 된다.
+ 어떻게 글로벌 헤더를 설정할까?
기존의 OpenAPI description에 전역 매개변수(글로벌 파라미터)가 있다.
만약에 전역적으로 모든 그룹에서 나타나게 정의하고 싶다면, OpenAPI Bean을 사용하기만 하면 된다.
global components 섹션 아래 파라미터 자리에 공통 파라미터를 정의하고(.components) $ref를 통해 다른 곳에서도 참조하게 할 수 있다. 또한 글로벌 헤더 파라미터도 새로 정의할 수 있다(.addParameters 자리에서)
OpenAPI Bean을 오버라이딩 할 수 있고 글로벌 헤더를 수정(set)하거나 파라미터들의 정의를 .components 자리에서 할 수 있다.
@Bean
public OpenAPI customOpenAPI(@Value("${springdoc.version}") String appVersion) {
return new OpenAPI()
.components(new Components().addSecuritySchemes("basicScheme", new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("basic"))
.addParameters("myHeader1", new Parameter().in("header").schema(new StringSchema()).name("myHeader1")).addHeaders("myHeader2", new Header().description("myHeader2 header").schema(new StringSchema())))
.info(new Info()
.title("Petstore API")
.version(appVersion)
.description("This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.")
.termsOfService("http://swagger.io/terms/")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}
도메인 로직과 어플리케이션 서비스 로직을 구분하는 기준은, '비즈니스 의사결정'이다.
" 이 코드가 현실 문제, 즉 비즈니스에 대한 의사결정을 하고 있는가? "
이거 하나만 기억하면 된다.
도메인 로직은 현실 문제에 대한 의사결정을 하는 코드다. 나머지 코드는 그 결정을 위한 입력값을 만들어주거나, 그 결정의 결과물을 해석하고 보여주고 전파하는 코드다.
도메인 로직은,
소프트웨어가 존재하는 이유, 목적을 가리킨다.
현재 코드가 비즈니스에 중요한 의사결정을 내리는 코드라면 그 코드는 Domain Logic으로 들어가야 한다. (그렇기 때문에 domain logic은 business logic이라고도 부른다)
반대로 말해 어플리케이션 서비스 로직은,
비즈니스에 중요한 결정을 내리는 코드는 작성하면 안 되며, 이러한 중요 결정들은 모두 도메인 로직에서 처리하도록 위임하는 것이 올바르다.
중요 결정들은 모두 도메인 로직에게 위임하고 특정 어플리케이션 내에서 비즈니스 규칙을 구현하는 코드를 어플리케이션 서비스 로직에 작성하면 된다.
은행 앱이라면, 금융 및 은행 업무가 도메인이다. 은행 앱이 해결하고자 하는 문제가 금융 업무를 스마트폰에서 처리할 수 있게 해주는 것이기에. 틱톡 같은 SNS라면 동영상 촬영, 감상, 댓글 및 공유에 해당. 배달 앱이라면 결제, 배달 업무에 해당.
공학/기술적인 문제에 속하는 것들.
수많은 은행 사용자 데이터를 어떻게 효율적으로 저장할 것인가. 어떻게 고화질 동영상을 빠르게 로딩할 것인가? 같은 것들.
도메인 로직에 해당하는 것은 다음과 같다.
'송금'에 대한 의사결정을 담당.
계좌 잔액이 충분한지 확인 -> 송금이 가능한지에 대한 의사결정
송금 수수료를 계산 -> 송금에 드는 비용을 정책에 따라서 결정
사용자의 잔액을 감소시킨다 -> 송금이라는 서비스를 수행
어플리케이션 서비스 로직에 해당하는 것은 다음과 같다.
도메인 로직이 의사결정을 할 수 있도록 입력을 제공하며, 결과를 외부 서비스 / DB / UI에 업데이트하는 역할을 맡는다.
유효하지 않으면 에러 메시지를 띄운다 -> UI
송금 수수료를 결제하도록 외부 결제 서비스에 요청한다. -> 외부 서비스와의 네트워킹
잔액을 DB에 저장한다. -> Persistence
영속성(persistence)은 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지 않는 데이터의 특성을 의미
미국 달러 환율을 얼마로 할 것인가. 계산 공식은 얼마로 할 것인지 결정하는 코드 -> 도메인 로직
단위와 화폐 포맷을 바꿔서 UI에 업데이트를 하는 코드 -> 어플리케이션 서비스 로직
도메인 로직과 어플리케이션 서비스 로직은 어떤 앱이든 역할이 구분된다.
도메인 로직과 아닌 것을 잘 나누고 결합도를 낮추면, 개발자가 로직을 이해하기 쉬워진다.
DB나 UI 같은 기술적인 구현 사항에 신경 쓰지 않고, 도메인 로직을 이해할 수 있다.
비즈니스 정책이 바뀔 때 소프트웨어를 변경하고 기능을 추가하는 것이 편해진다.
이런 이유로 도메인 로직을 앱의 코어로 두고, 다른 계층에 의존하지 않도록 설계한다.
다른 계층들은 도메인 로직에게 입력을 전달하고, 변화를 외부로 전달하는 역할을 하도록 명확하게 분리한다.
Domain Service, Application Service는 entity, VO를 상위에서 다루는 stateless한 클래스를 의미한다. 이렇게 보면 둘은 꽤 유사해 보인다. 그러나 도메인 서비스는 도메인 로직을 갖고 있지만, 애플리케이션 서비스는 그렇지 않다는 점에서 큰 차이점을 띈다.
비즈니스 의사결정과 관련된 코드를 담당한다.
Entity / VO에 속할 수 없는 도메인 로직을 다룰 때 사용한다.
비즈니스 로직을 실행할 때, 필요한 정보를 준비한다. => 이는 데이터베이스, API 호출 등을 통해 준비한다.
비즈니스 로직을 실행한다. => 1개 이상의 도메인 모델을 활용하여 비즈니스 의사결정을 실행한다. 이 과정을 통해 모델의 상태가 변경되거나, 필요한 정보가 생성된다.
비즈니스 로직의 실행 결과를 실제 세계에 반영한다. => 데이터베이스에 실행 결과를 저장하거나, 외부 서비스를 요청하여 실행 결과를 현실 세계에 반영한다.
비즈니스 로직이 도메인 서비스가 필요한 예시
돈을 출금하기 위해서는, 잔고를 확인한 후, 연동사에서 돈을 차감한 후에 돈을 출금해야한다. 이 때, 연동사에 돈을 차감하는 비즈니스 로직은 Entity, VO에 속할 수 없는 기능이다.
이와 같은 경우에는 Domain Service가 필요하다.
도메인 모델 안에 도메인 서비스, entity, vo 가 포함됨.
도메인 서비스는 현실에 반영되는 로직을 담당.
도메인 모델을 이용해 비즈니스 로직을 짜는데 entity, vo 기능으로는 해결 못함.
도메인 모델 내 도메인 서비스를 이용해 로직 짜는걸 해결.
어플리케이션 서비스는 요청의 처리에 대한 로직을 담당.
어플리케이션 서비스는 Service Layer에 속해 Client에게 최종 응답을 어플리케이션의 최상위 레이어 Web Layer로 넘겨주는 역할!
Client 호출과 CRUD는 어플리케이션 서비스가 담당!
참고:
https://springdoc.org/#how-can-i-set-a-global-header