- StringUtils.hasText(String str) 메서드
전달된 문자열 타입 매개변수가 1. null 이 아니어야 하고,
2. 빈 문자열("")이 아니어야 하고, 3. 빈 공백으로 구성된 문자열(" ")이 아니어야
true 를 반환하는 기능을 갖는다.
*** 이제까지 isBlank() 메서드가 null, 빈 문자열, 공백으로 이루어진 문자열을 체크하는 메서드라고 생각했는데,
isBlank() 메서드는 빈 문자열인지 공백으로 이루어진 문자열인지 2가지만 체크하는 기능이라는 걸 알게되었다.
String.isBlank() 메서드를 사용하기 전에는 문자열 타입의 참조변수의 null 여부를 먼저 체크해야 한다는 사실을 알게됨.
// String.isBlank() 사용법
// 클라이언트로부터 데이터를 받는 경우에는 requestDto 클래스 자체에서 null 을 허용하지 않는 유효성 검증 또는
// 서비스 계층에서 null 체크를 하던지 두 가지 방법 중 하나를 택하면 될 것 같다.
// dto 에서 null 유효성 검증을 진행하지 않았을 때
// dto 객체가 갖는 userName 의 유효성 검증을 서비스 계층에서 체크한다는 가정하에
// dto 객체가 갖는 userName 이 null 이 아니고, 빈 공백이 아니고, 빈 공백으로 이루어진 문자열이 아님을 검증하기
if(dto.getUserName() != null && !dto.getUserName().isBlank())
// StringUtils.hasText(String str) - 전달된 문자열 타입 변수의 값이 null 인지 공백인지 확인하는 메서드
@Contract("null -> false")
public static boolean hasText(@Nullable String str) {
// 1. str != null: 전달된 매개변수가 null 이 아니어야 true 를 반환함
// 2. !str.isBlank(): 전달된 매개변수가 빈문자열 또는 빈공백으로만 이루어진 문자열이 아니어야 true 를 반환
// 1, 2 번 조건을 모두 만족해야 true 를 반환함.
// 즉, hasText() 메서드는 null, 빈문자열, 빈문자열로 이루어진 문자열을 체크(검증)하는 기능을 갖는다.
return str != null && !str.isBlank();
}
SpringSecurity
SpringSecurity 는 인증 및 인가를 처리하기 위해 Filter를 사용하는데
FilterChainProxy를 통해서 상세로직을 구현한다.
SpringSecurity 는 필터 기반으로 작동한다.
SpringSecurity default 로그인이 아닌 내가(예비 개발자) 직접 커스텀해서 사용하기
UserDetailsImpl 과 UserDetailsServiceImpl 을 커스텀하게 되면
SpringSecurity 가 제공하는 기본 로그인을 사용하지 않겠다는 설정이 된다.
서버 실행시 터미널창에 default password 가 찍히지 않는다.
UserDetailsServiceImpl 을 커스텀한다는 것은
인터페이스인 UserDetailsService 을 구현한 클래스를 만드는 것을 의미한다.
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
}
// 인터페이스 UserDetailsService 를 구현한 클래스를(UserDetailsServiceImpl) 작성할 때
// db 에 존재하는 user 테이블에 접근할 수 있는 UserRepository 타입의 객체를 갖은채로
// UserDetailsServiceImpl 객체 생성하기
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
public UserDetailsServiceImpl(UserRepository userRepository){
this.userRepository = userRepository;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Not Found "+username));
return new UserDetails(user);
}
}
SpringSecurity 는 기본적으로 세션방식이다.
세션방식이 아닌 jwt 로 인증/권한 확인하는 방식으로 커스텀하기
- 기존 로그인 방식은 컨트롤러에서 로그인을 처리함(로그인 로직 정상 수행시 jwt를 생성하고, 클라이언트에 반환)
이제는 필터에서 로그인 및 jwt를 생성하고 클라이언트에 반환하는 방식으로 진행할 예정.
먼저 커스텀 필터 클래스는 UsernamePasswordAuthenticationFilter 를 상속한다.
UsernamePasswordAuthenticationFilter 는 사용자의 username과 password를 전달받으면
username과 password로 토큰 만들고, AuthenticationManager를 통해서 인증을 한다.
클라이언트로부터 전달된 값을 담은 객체의 유효성 검증하기
유효성을 검증하는 애너테이션
종류 | 기능설명 |
@NotNull | null 불가 |
@NotEmpty | null, ""(빈 문자열) 불가 |
@NotBlank | null, ""(빈 문자열), " "(공백으로 구성된 문자열) 불가 |
@Size | 문자 길이 측정 |
@Min | 최소값 |
@Max | 최대값 |
e-mail 형식이어야 한다. 필드값에 어노테이션 기호(@)가 포함되어 있어야 한다. |
예시
커맨드 객체(컨트롤러 계층의 메서드에서 매개변수로 받는 VO객체)가 갖는 필드 값의 유효성을 검증할 때
애너테이션별 속성 설정으로 세부적인 값 체크 등을 할 수 있다.
상기처럼 클라이언트로부터 전달받는 requestDto 클래스에 유효성을 검증하는 애너테이션을 작성한 뒤에
커맨드 객체를 전달받는 컨트롤러 계층의 메서드 매개변수 앞에 @Valid 어노테이션을 작성해주어야 한다.
그리고 유효성 검증을 통과하지 못했을 때 발생한 에러는 BindingResult 타입 참조변수가 참조할 수 있도록
BindingResult 타입 객체도 함께 작성해주도록한다.
// Controller 계층
@PostMapping("/user/signup")
public String signUp(@Valid SignUpRequestDto dto, BindingResult result){
// 1. 커맨드 객체 앞에 @Valid를 작성하면 객체가 갖는 필드 값의 유효성을 검증한다.
// 2. 필드 값 유효성 검증에 문제가 생기면 BingdingResult 객체에 담긴다.
List<FieldError> fieldErrors = result.getFieldErrors();
if(fieldErrors.size() > 0){
for(FieldError fieldError : fieldErrors) {
log.error(fieldError.getField()+" 필드: "+fieldError.getDefaultMessage());
}
return /* 유효성 검증에 문제가 생겼고, 이 때 클라이언트에게 반환할 페이지 경로 작성해주기 */;
}
/*
클라이언트로부터 전달된 값의 유효성 문제는 없고,
전달된 값을 받아 로직을 수행한 뒤
문제가 없다면 그 때 클라이언트에게 반환할 페이지 경로 작성해주기
*/
return // ...
}
MSA 기반 어플리케이션 개발(Spring Cloud)
하나의 프로젝트에 담긴 모든 로직들을 큰 단위의 도메인으로 나누고 독립적인 어플리케이션으로 개발한다.
예를 들면 주문 서비스, 상품 서비스, 유저 서비스 등이다.
주문 서비스에서 주문 생성의 기능을 수행하려면 주문을 확인해야 하고
주문 정보에 기반한 상품을 확인해야 하고 주문한 유저를 확인해야 한다.
이 과정에서 주문 서비스가 상품 서비스와 유저서비스를 호출하는데
주문 서비스에서는 호출하고자 하는 서비스의 위치를 알아야 한다.
그 위치를 알려주는 게 Eureka Server다.
Eureka Server 에 등록된 모든 서비스(어플리케이션)들은 Eureka Client가 된다.
각 서비스의 host는 다르고 클라이언트가 각 서비스를 호출할 때 서비스별 host를
호출하는 불편함을 방지하기 위해 API 게이트웨이(ex. Cloud gateway)를 설정해준다.
Eureka Server는 모든 서비스의 위치 정보를 등록하고
각 서비스에서 feignClient를 사용해 다른 서비스를 호출할 때
서비스간 호출을 가능하게 해주는(호출할려는 서비스의 위치를 알려줌)
중간 역할을 하는 것으로 이해함.
API gateway는 클라이언트의 입장에서 요청한 url을 기준으로
해당 요청을 처리할 서비스에게 요청을 전달하는 중간 역할을 하는 것으로 이해함.
Eureka Server는 앱관점, API gateway는 클라이언트 관점
그리고 서비스간 호출에 문제가 생겼을 때 해당 에러는
R4j를(서킷 브레이커) 통해 확인하고 에러에 대응한다.
EurekaServer 설정
1. 스프링 프레임워크 기반 프로젝트의 build.gradle의 dependencies에 하기와 같은 의존성을 추가한다.
// build.gradle
dependencies{
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
}
2. 설정파일을 다음과 같이 작성한다.
# application.yml
server:
port: # Eureka Server port 번호 작성하기
eureka:
client:
register-with-eureka: false # 다른 eureka server에 이 서버를 등록하지 않는다.
fetch-registry: false # 다른 유레카 서버의 레지스트리를 가져오지 않음.
server:
enable-self-preservation: false # 자기 보호 모드 비활성화
EurekaClient 설정
각 서비스 어플리케이션이 EurekaServer에 자신의 위치를 등록하는 방법
1. build.gradle의 dependency에 eureka client 의존성 추가하기
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
2. yml 파일에 어플리케이션 이름 설정하기
# application.yml
spring:
application:
name: # eureka server 에 위치와 함께 등록될 어플리케이션 이름
EurekaServer에 등록된 EurekaClient는 EurekaServer에서 필요한 서비스의 위치를 조회한다.
FeignClient
Spring Cloud에서 제공하는 HTTP 클라이언트로 선언적으로 Restful API를 호출할 수 있다.
여기서 '선언적으로' 라는 의미는 API 호출을 명시적으로 코딩하지 않고, 주로 어노테이션을 통해 호출을 정의하는 방식을 의미한다.
Eureka와 같은 서비스 디스커버리와 연동하여 동적으로 서비스 인스턴스를 조회하고 로드 밸런싱을 수행한다.
FeignClient는 리본(Ribbon)이 통합되어 있어 FeignClient를 사용하면 자동으로 로드 밸런싱을 수행한다.
서비스 디스커버리:
로드 밸런싱:
Ribbon
넷플릭스가 개발한 클라이언트 사이드 로드 밸런서, MSA에서 서비스 인스턴스간의 부하를 분산한다.
FeignClient와 Ribbon의 동작원리
1. 현재 서비스에서(어플리케이션) 호출하고자 하는 서비스를 인터페이스로 작성한다.
해당 인터페이스에 @FeignClient 어노테이션을 선언하고, 속성으로 name을 설정한다.
name의 값으로는 Eureka에 등록된 호출하고자 하는 서비스 이름을 작성한다.
2. 현재 서비스에서 @FeignClient 어노테이션에 설정한 name의 값에 기반하여 Eureka 서버에 등록된
서비스 인스턴스의 목록을 조회한다.
3. 조회된 서비스 인스턴스의 목록 중 하나를 선택하여 요청을 보낸다. Ribbon을 사용하여 로드 밸런싱을 수행한다.
4. 여러 서비스가 존재할 경우 Round Robbin 또는 다른 설정된 로드 밸런싱 알고리즘을 사용하여 요청을 분배한다.
gateway-server 설정파일(application.yml)
# gatway-server 설정파일
spring:
application:
name: gateway-service
main:
web-application-type: reactive #webflux 기반 동작
# spring.cloud.gateway.routes: spring cloud gateway에서 라우팅을 설정하는 핵심 부분이다.
# id는 라우트를 식별하는 고유 식별자다. /api/hospitals/로 시작하는 요청 url이 오면
# eureka-server에 등록된 hospital-service를 참조한다.
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
- id: hospital-service
uri: lb://hospital-service
predicates:
- Path=/api/hospitals/**
4계층 아키텍쳐 적용
패키지별 구조
- infrastructure: 인프라 계층
- application: 응용 계층(서비스 계층)
- presentation: 표현 계층
- domain: 도메인 계층
BaseEntity: jpa가 관리하는 entity 클래스에 공통으로 적용되는 필드를 모아놓은 (추상) 클래스
package com._7.bookinghospital.hospital_service.domain.model;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter
// @MappedSuperclass: jpa 가 관리하는 entity class 들이 해당 클래스(BaseEntity)를 상속할 경우
// (추상) 클래스에 선언한 멤버 변수를(createdAt, createdBy ...) 컬럼으로 인식해주는 기능을 갖는다.
@MappedSuperclass
// @EntityListeners(AuditingEntityListener.class): BaseEntity 클래스에 jpa auditing 기능을 추가해주는 것
// 이 어노테이션을 작성해주어야 해당 클래스가 가진 시간 필드에 자동으로 시간 데이터를 삽입해준다.
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(updatable=false)
protected LocalDateTime createdAt;
@CreatedBy
protected Long createdBy;
@LastModifiedDate
protected LocalDateTime updatedAt;
@LastModifiedBy
protected Long updatedBy;
protected boolean is_deleted;
// *** delete() 메서드 호출시 updatedAt 과 updatedBy 자동으로 데이터 입력될까?
public void delete(Long deletedBy) {
this.is_deleted = true;
}
}
'[부트캠프] kdt 심화 과정' 카테고리의 다른 글
docker 관련 정리(1) (0) | 2025.04.22 |
---|---|
4계층, JPA와 리플렉션, JPA auditing 기술 (1) | 2025.04.21 |
MSA(Microservice Architecture)_컨피그 서버(Config Server) (0) | 2025.03.13 |
MSA(Microservices Architecture) 서킷브레이커 (0) | 2025.03.12 |
MSA(Microservices Architecture) 서비스 디스커버리, 로드 밸런싱 (0) | 2025.03.11 |