네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'web/Spring'에 해당되는 글 67건


반응형

[@Autowired 빈 탐색 전략과 @Qualifier, @Primary]

1. 빈(Bean) 등록과 조회 규칙

[ 빈 등록 ]

Spring은 기본적으로 메서드의 이름을 Bean의 이름으로 사용한다. 하지만 개발자가 직접 빈의 이름을 부여할 수도 있다.

@Bean
public DiscountPolicy fixDiscountPolicy() {
    return new FixDiscountPolicy();
}

@Bean("fixDiscountPolicy")
public DiscountPolicy fixDiscountPolicy() {
    return new FixDiscountPolicy();
}

 

[ 빈 조회 규칙 전략 ] 

@Autowired가 등록된 빈을 찾을 때에는 다음과 같은 매칭 규칙으로 빈을 조회한다.

  1. 주고받고자 하는 타입으로 매칭을 시도한다.
  2. 타입이 여러 개면 필드 또는 파라미터 이름으로 매칭을 시도한다.

 

하지만 빈의 이름이 충돌되어 빈 이름만으로 해결이 불가능한 경우나 빈에 추가 구분자나 우선순위를 부여하고 싶은 경우에 @Qualifier나 @Primary 어노테이션을 이용해 편리하게 해결할 수 있다.

 

추가로 Spring은 @Resource라는 어노테이션도 제공하고 있다. @Resource는 @Autowired와 달리 필드 이름으로 빈을 찾는다.

  • @Autowired: 필드 타입을 기준으로 빈을 찾음
  • @Resource: 필드 이름을 기준으로 빈을 찾음

 

2. @Qualifier와 @Primary

[ @Qualifier - 빈의 Alias(구분자) ]

빈의 이름만으로 부족하고, 추가적인 정보가 필요할 수 있다. 그런 상황에서 @Qualifier 어노테이션을 통해 빈에 추가 구분자를 붙여줄 수 있다.

@Qualifier("mainDiscountPolicy")
@Bean
public DiscountPolicy fixDiscountPolicy() {
    return new FixDiscountPolicy();
}

다음과 같이 빈을 찾고자하는 경우에 @Qualifier 어노테이션을 부여하여 빈을 찾도록 할 수 있다.

public class DiscountServie {

    private final DiscountPolicy discountPolicy;
    
    @Autowired
    public DiscountService(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
}
  1. 해당 @Qualifier가 붙은 빈을 조회한다.
  2. @Qualifier가 붙은 빈을 찾지 못하면 필드명 또는 파라미터명으로 매칭을 시도한다. 
  3. 그래도 찾지못하면 NoSuchBeanDefinitionException 이 발생한다.

[ @Primary - 빈의 우선순위 부여 ]

여러 타입의 빈이 존재할 때, 특정 빈을 우선적으로 주입하도록 하고 싶다면 @Primary 어노테이션을 사용할 수 있다. Spring이 타입으로 비을 찾다가 Primary가 붙어있는 빈을 발견하면, 바로 해당 빈을 주입시킨다. 즉, @Primary는 여러 개의 빈들 중에 우선순위를 부여하는 방법이다.

@Primary
@Bean
public DiscountPolicy fixDiscountPolicy() {
    return new FixDiscountPolicy();
}

만약 @Primary와 @Qualifier 모두 등록되어있다면 Qualifier가 우선순위를 갖는다.

 

[ 빈 충돌 발생 ]

빈 등록 시, 충돌이 발생한다면

  •  자동 빈 등록 vs 자동 빈 등록: 빈 이름 중복 에러가 발생한다.
  • 수동 빈 등록 vs 자동 빈 등록: 과거에는 수동 빈이 우선순위였지만 최근에는 에러가 발생한다.
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

개발을 하다보면 API의 요청이나 응답을 처리할 때 또는 다른 계층으로 넘기는 파라미터가 너무 많은 시점에 별도의 DTO를 생성하는 것이 좋습니다.

 

[엔티티(Entity) 또는 도메인 객체(Domain Object)와 DTO를 분리해야 하는 이유]

1) 관심사의 분리

2) Validation 로직 및 불필요한 코드 등과의 분리

3) API 스펙과의 분리

4) API 스펙의 파악이 용이

 

1. 관심사의 분리

엔티티와 DTO를 분리해야 하는 가장 근본적인 이유는 관심사가 서로 다르기 때문이다. 관심사의 분리(separation of concerns, SoC)는 소프트웨어 분야의 오래된 원칙 중 하나로서, 서로 다른 관심사들을 분리하여 변경 가능성을 최소화하고, 유연하며 확장가능한 클린 아키텍처를 구축하도록 도와준다.

DTO(Data Transfer Object)의 핵심 관심사는 이름 그대로 데이터의 전달이다. DTO는 데이터를 담고, 다른 계층(Layer) 또는 다른 컴포넌트들로 데이터를 넘겨주기 위한 자료구조(Data Structure)이다. 그러므로 어떠한 기능 및 동작도 없어야 한다.

반면에 엔티티는 핵심 비즈니스 로직을 담는 비즈니스 도메인 영역의 일부이다. 그러므로 엔티티 또는 도메인 객체는 그에 따른 비즈니스 로직이 추가될 수 있다. 엔티티 또는 도메인 객체는 다른 계층이나 컴포넌트 사이에서 전달을 위해 사용되는 객체가 아니다.

엔티티와 DTO는 엄연히 서로 다른 관심사를 가지고 있고, 그렇기 때문에 분리하는 것이 합리적이다.

 

2. Validation 로직 및 불필요한 코드 등과의 분리

Spring 에서는 요청에 대한 데이터를 검증하기 위해 @Valid 어노테이션을 지원하고 있다.

@Valid 처리를 위해서는 @NotNull, @NotEmpty, @Size 등과 같은 유효성 검증 어노테이션들을 변수에 붙여주어야 한다. 반면에 JPA도 변수에 @Id, @Column 등과 같은 어노테이션을 활용해 객체와 관계형 데이터베이스를 매핑해주는데, DTO와 엔티티를 분리하지 않는다면 엔티티의 코드가 상당히 복잡해진다. 

또한, 엔티티 클래스의 생성일자, 수정일자를 나타내는 변수들은 API 요청이나 응답에서는 필요가 없다. 그렇기 때문에 응답에서 해당 변수를 제거하기 위해서는 @JsonIgnore 등과 같은 또 다른 어노테이션을 통해 별도의 작업이 필요할 수도 있다.

이렇게 엔티티 클래스를 사용하게 되면 핵심 비즈니스 도메인 코드들이 아닌 요청/응답을 위한 값, 유효성 검증을 위한 코드 등이 추가되면서 엔티티 클래스가 지나치게 비대해질 것이고, 확장 및 유지보수 등에서 매우 어려움을 겪게 될 것이다. 

그러므로 이러한 경우에는 별도의 DTO를 생성해서 엔티티로부터 분리하는 것이 바람직할 것이다.

 

3. API 스펙의 유지

@Entity 
@Table 
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Membership {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    private Long id;
    
    @Enumerated(EnumType.STRING)
    private MembershipType membershipType; 
    
    @Column(nullable = false) 
    private String userId;
    
    @Setter 
    @Column(nullable = false) 
    @ColumnDefault("0") 
    private Integer point;
}
{ 
    "id" : "15", 
    "membershipType" : "NAVER", 
    "userId" : "NC10523", 
    "point" : "10000" 
}

내부 정책 변경으로 userId를 memberId로 변경해야 하는 상황이다. DTO를 사용하지 않는다면 userId가 memberId로 바꿈에 따라 API 스펙이 변경되고, API를 사용하던 사용자들은 모두 장애를 겪게 될 것이다. 물론 @JsonProperty를 이용해 반환되는 값의 이름을 변경할 수 있지만, 이는 결국 Entity를 무겁게 만들어 근본적인 해결책이 될 수 없다.
스펙이 변경되어 테이블에 컬럼이 추가되는 경우도 마찬가지이다. 테이블에 새로운 컬럼이 추가되면 엔티티에 새로운 변수가 추가될 것이고, 별도로 처리를 하지 않는 이상 API 스펙이 변경되는 것이다.

이러한 상황을 위해 DTO를 이용해 분리하여 독립성을 높임으로써 변경이 전파되는 것을 방지해야 한다. 만약 우리가 응답을 위한 DTO 클래스를 활용하고 있으면, Entity 클래스의 변수가 변경되어도 API 스펙이 변경되지 않으므로 안정성을 확보할 수 있다.

 

4. API 스펙의 파악 용이

DTO를 사용함으로써 얻는 또 다른 장점은 DTO를 통해 API 스펙을 어느정도 파악할 수 있다는 점이다. API 문서의 요약본을 작성하는 것과 유사한 효과를 얻을 수 있으며, 특히 요즘같은 MSA 아키텍처로 개발을 많이 하는 상황에서 다른 사람이 작성한 API 호출 코드를 파악할 때 요청/응답을 손쉽게 파악할 수 있어 용이하다.

 

출처

https://mangkyu.tistory.com/192

 

[Spring] 엔티티(Entity) 또는 도메인 객체(Domain Object)와 DTO를 분리해야 하는 이유

개발을 하다 보면 API의 요청이나 응답을 처리할 때 또는 다른 계청으로 넘기는 파라미터가 너무 많은 시점에 별도의 DTO를 생성해야 하나 고민을 하는 시점이 생깁니다. 개인적으로는 간단한 애

mangkyu.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

EntityGraph란,

- 엔티티들은 서로 연관되어 있는 관계가 보통이며 이 관계는 그래프로 표현이 가능하다. EntityGraph는 JPA가 어떤 엔티티를 불러올 때 이 엔티티와 관계된 엔티티를 불러올 것인지에 대한 정보를 제공한다.

 

// getter/setter ...
@Entity
@Table(name = "user")
public class User {

    // other properties
    
    @ToString.Exclude
    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private List<Address> addresses = new ArrayList<>();
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findById(Long userId);
    
    @EntityGraph(attributePaths = {"addresses"}, type = EntityGraph.EntityGraphType.LOAD)
    Optional<User> findWithAddressesById(Long userId);
}

addresses 가 LAZY이기 때문에 findById로 패치한 User 엔티티에서 addresses에 접근하면 그 때 달려있는 addresses 개수 만큰 select 쿼리를 날린다. 하지만 findWithAddressesById는 @EntityGraph 어노테이션으로 addresses도 함께 패치해오도록 해두었기 때문에 1번의 fetch join 쿼리만 실행된다.

 

/*
 * findById 쿼리
 */
select
    user0_.id as id1_10_0_,
    user0_.username as username2_10_0_
from
    user user0_
where
    user0_.id=?

/*
 * @EntityGraph 달린 findWithAddressesById 쿼리
 */
select
    user0_.id as id1_10_0_,
    addresses1_.id as id1_5_1_,
    user0_.username as username2_10_0_,
    addresses1_.street as street2_5_1_,
    addresses1_.user_id as user_id3_5_0__,
    addresses1_.id as id1_5_0__
from
    user user0_
left outer join
    address addresses1_
        on user0_.id=addresses1_.user_id
where
    user0_.id=?

fetch type을 바꿀 필요도 없고, Querydsl이나 JPQL 같은 쿼리를 별도로 만들지 않아도 되기 때문에 편하다.

 

@EntityGraph의 type은 EntityGraph.EntityGraphType.FETCH 와 EntityGraph.EntityGraphType.LOAD 두가지가 있다.

- FETCH: entity graph에 명시한 attribute는 EAGER로 패치하고, 나머지 attribute는 LAZY로 패치

- LOAD: entity graph에 명시한 attribute는 EAGER로 패치하고, 나머지 attribute는 entity에 명시한 fetch type이나 디폴트 FetchType으로 패치 ( @OneToMany는 LAZY, @ManyToOne은 EAGER 등이 디폴트이다. )

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

스프링부트 액추에이터 사용하기

 

액추에이터는 스프링 부트 애플리케이션의 모니터링이나 매트릭(metric)과 같은 기능을 HTTP와 JMX 엔드포인트를 통해 제공한다.

 

액추에이터 개요

액추에이터는 실행 중인 애플리케이션의 내부를 볼 수 있게 하고, 어느 정도까지는 애플리케이션의 작동 방법을 제어할 수 있게 한다. 예를 들면, 다음과 같다.

 

* 애플리케이션 환경에서 사용할 수 있는 구성 속성들

* 애플리케이션에 포함된 다양한 패키지의 로깅 레벨(logging level)

* 애플리케이션이 사용 중인 메모리

* 지정된 엔드포인트가 받은 요청 횟수

* 애플리케이션의 건강 상태 정보

 

스프링 부트 애플리케이션에 액추에이터를 활성화하려면 의존성을 빌드에 추가해야 한다.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

 

액추에이터 엔드포인트

HTTP 메서드 경로 설명 디폴트 활성화
GET /auditevents 호출된 감사(audit) 이벤트 리포트를 생성한다.  NO
GET /beans 스프링 애플리케이션 컨텍스트의 모든 빈을 알려준다. NO
GET /conditions 성공 또는 실패했던 자동-구성 조건의 내역을 생성한다. NO
GET /configprop 모든 구성 속성들을 현재 값과 같이 알려준다. NO
GET,POST,DELETE /env 스프링 애플리케이션에 사용할 수 있는 모든 속성 근원과 이 근원들의 속성을 알려준다. NO
GET /env/{toMatch} 특정 환경 속성의 값을 알려준다. NO
GET /health 애플리케이션의 건강 상태 정보를 반환한다. YES
GET /heapdump 힙(heap) 덤프를 다운로드한다. NO
GET /httptrace 가장 최근의 100개 요청에 대한 추적 기록을 생성한다. NO
GET /info 개발자가 정의한 애플리케이션에 관한 정보를 반환한다. YES
GET /loggers 애플리케이션의 패키지 리스크(각 패키지의 로깅 레벨이 포함된)를 생성한다. NO
GET /loggers/{name} 지정된 로거의 로깅 레벨(구성된 로깅 레벨과 유효 로깅 레벨 모두)를 반환한다. 유효 로깅 레벨은 HTTP POST 요청으로 설정될 수 있다. NO
GET /mappings 모든 HTTP 매핑과 이 매핑들을 처리하는 핸들러 메서드들의 내역을 제공한다. NO
GET /metrics 모든 메트릭 리스트를 반환한다. NO
GET /metrics/{name} 지정된 메트릭의 값을 반환한다. NO
GET /scheduledtasks 스케줄링된 모든 태스크의 내역을 제공한다. NO
GET /threaddump 모든 애플리케이션 스레드의 내역을 반환한다. NO

액추에이터의 기본 경로 구성하기

액추에이터의 모든 앤드포인트의 경로에는 /actuator가 앞에 붙는다. 액추에이터의 기본 경로는 management.endpoint.web.base-path 속성을 설정하여 변경할 수 있다.

management:
  endpoints:
    web:
      base-path: /management

 

액추에이터의 엔드포인트의 활성화와 비활성화

액추에이터를 추가하면 /health 와 /info 엔드포인트만 기본적으로 활성화되는 것을 알 수 있다. 대부분의 액추에이터 엔드포인트는 민감한 정보를 제공하므로 보안 처리가 되어야 하기 때문이다. 물론 스프링 시큐리트를 사용해서 액추에이터를 보완처리할 수 있다. 그러나 액추에이터 자체로는 보안처리가 되어있지 않으므로 엔드포인트가 기본적으로 비활성화되어 있다.

엔드포인트의 노출 여부를 제어할 때는 management.endpoints.web.exposure.include와 management.endpoints.web.exposure.exclude 구성 속성을 사용할 수 있다.

management:
  endpoints:
    web:
      exposure:
        include: health, info, beans, conditions
        exclude: threaddump, heapdump
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

서버의 상태를 확인하려면 Health Check를 사용하면 된다.

implementation('org.springframework.boot:spring-boot-starter-actuator')

- Health Check 기능은 spring-boot-starter-actuator 라이브러리의 기능 중 하나이므로 사용하려면 actuator 라이브러리를 추가해야 한다.

- spring-boot-starter-actuator 라이브러리는 Spring Boot 버전과 동일한 버전을 사용해야 한다.

management:
  endpoints:
    web:
      base-path: /application
      path-mapping:
        health: healthcheck
  endpoint:
    health:
      show-details: always

base-path: actuator의 base path를 설정한다. (기본값은 /actuator)

path-mapping.health: health-check end point (기본값은 health)

show-details: health check API를 접근할 때 세부 정보를 확인한다. (never, when-authorized, always/ 기본값은 never)

 

# show-details: never로 설정
{"status": "UP"}

# show-details: always로 설정
{"status": "UP", 
 "details": {
   "diskSpace": {
     "status: "UP", 
     "details": {
       "total": 234685313024,
       "free": 158591512576,
       "threshold": 10485760
     }
   },
   "redis": { 
     "status":"UP",
     "details":{"version":"5.0.7"}
   },
   "db":{
     "status":"UP",
     "details":{
       "database":"MariaDB",
       "hello":1
     }
   }
 }
}
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !