네로개발일기

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

'전체 글'에 해당되는 글 194건


반응형

개발을 하다보면 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

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

반응형

nullable한 두 개의 String의 equality 비교를 어떻게 null safe하게 할 수 있을까?

 

java.util.Objects.equals(Object, Object)

Java7부터 java.utils.Objects의 static 메서드 equals(Object, Object)를 사용할 수 있다. 이 메서드는 둘다 null이면 true를, 둘 중 하다가 null이면 false를, 그렇지 않으면 equals의 결과를 리턴한다.

 

nullable한 객체에서 메소드를 호출하는게 아니라 static 메소드이므로 NullPointerException의 발생 가능성이 없고 별도의 라이브러리를 추가할 필요가 없다는 장점이 있다.

public final class Objects {

    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }
}

StringUtils.equals(CharSequence, CharSequence) - Apache Commons

다른 방법으로는 apache commons 라이브러리의 StringUtils가 제공하는 static 메소드 equals()를 사용할 수 있다.

public class StringUtils {

    public static boolean equals(CharSequence cs1, CharSequence cs2) {
        if (cs1 == cs2) {
            return true;
        } else if (cs1 != null && cs2 != null) {
            if (cs1 instanceof String && cs2 instanceof String) {
                return cs1.equals(cs2);
            } else {
                return cs1.length() == cs2.length() && CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, cs1.length());
            }
        } else {
            return false;
        }
    }
}

이를 사용하려면 apache commons 라이브러리 의존성을 추가해야한다.

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

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