13. 엔티티 validation

Alex·2024년 7월 12일
0

리팩토링

목록 보기
13/17
public class Accommodation {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String name;
    @Column(nullable = false)
}

저는 영속성 레이어에는 validation 을 걸지 않는 편 입니다.
코드에서는 걸더라도 결국 DDL 레벨에서는 안거는게 보통 입니다.
마이그레이션 등의 어려움이 있기 때문입니다.
영속성 레이어의 validation 에 의존하는 것 보다는 도메인 레이어의 validation 을 강화할 것 같습니다.

이런 내용이 있었다.

영한샘은 실용적인 관점에서 DTO 유효성 검사를 선호하신다고 한다.

의존관계

안정된 의존관계 원칙과 안정된 추상화 원칙에 대하여

안정된 의존 관계 원칙 (Stable Dependencies Principle, SDP)
"20장 패키지 설계의 원칙"을 보면 Stable Depencies Principle은 간단히 "의존은 안정적인 쪽으로 향해야 한다."라는 것을 뜻합니다.

쉽게 바뀔 것이라고 예상되는 패키지들은 바뀌기 어려운 패키지의 의존 대상이 되어서는 안됩니다.

쉽게 바뀔 수 있는 모듈에 무언가가 의존하기 시작하면 금세 이 모듈은 변경하기 어렵게 됩니다.

이것을 보면 안정된 의존 관계원칙으로 봤을 때 Repository는 절대로 Service나 Controller에 의존하면 안 됨을 알 수 있습니다. 마찬가지로 Service도 Controller에 있는 코드를 호출해서는 안됩니다.

그렇다면 이런 안정적인 설계가 필요한 코드는 설계의 유연성이 떨어지게 됩니다. 안정적이면서도 설계의 유연성을 높일 수 있는 방법은 무엇일까요?
바로 추상 클래스(abstract class)입니다.

어떤 패키지가 안정적이라면 확장할 수 있도록 추상 클래스들로 구성되어야 하며, 확장이 가능한 안정적인 패키지는 유연하며 따라서 설계를 지나치게 제약하지 않아야 합니다.

위에서 말한 Controller를 계산해보면 위에서 Controller의 불안정성을 1(불안정적)이라고 했고, Controller에 대해 전혀 인터페이스를 만들지 않을 것이므로 추상성은 0이 됩니다. 따라서,
Controller의 주계열로부터의 거리 = |Controller의 추상성 0 + Controller의 불안정성 1 – 1| = 0
0은 주계열 바로 위를 뜻하므로 좋습니다.

다시 위에서 말한 Repository를 계산해보면 위에서 Repository의 불안정성을 0(안정적)이라 했고, Repository는 항상 인터페이스를 구현하도록 했으므로 추상성은 1이 됩니다. 따라서,
Repository의 주계열로부터의 거리 = |Repository의 추상성 1 + Repository의 불안정성 0 – 1| = 0
마찬가지로 0은 주계열 바로 위를 뜻하므로 좋습니다.

ConfigurationProperties


@Configuration
public class AwsConfig {

    @Value("${cloud.aws.credentials.accessKey}")
    private String accessKey;
    @Value("${cloud.aws.credentials.secretKey}")
    private String secretKey;
    @Value("${cloud.aws.region.static}")
    private String region;
    

이런 property를 주입받을 때 @Value로 받게 되면 final로 선언할 수 없어 약간 아쉬운데요. 다른 방법이 몇 개 있는데 찾아보시면 좋을 것 같아요.

@SpringBootApplication
@ConfigurationPropertiesScan  // 내부에 EnableConfigurationProperties 가지고 있음
public class TestApplication {


@ConfigurationProperties(prefix = "user")
public class TestConfig {

    private final String name;
    private final Long age;

    @ConstructorBinding
    public TestConfig(String name, Long age) {
        this.name = name;
        this.age = age;
    }
    

스프링부트 3이상부터는 @ConstructorBinding가 없어도 된다고 한다.

필드에 final없이 value로 넣어주는 방식은 spring 컨테이너가 싱글톤으로 관리하는 환경상 좋지 않다고 한다. 이 값이 언제든 변경될 수 있는 탓이다.

래퍼 클래스

    @Column(nullable = false)
    private Long pricePerNight;

    @Column(nullable = false)
    private Integer maxGuests;
    

DB column은 nullable = false인데 어플리케이션의 Data type은 nullable인 Wrapper type일 이유가 있을지 고민이 필요해보여요!

트랜잭션

쓰기 작업을 하는 비즈니스 로직에서는 보통 롤백 등을 위해 트랜잭션 위에서 동작하도록 하는데요. 한번 찾아보시고 적용하시는 걸 추천드립니다!

  • 트랜잭션이란?
    물건을 구매하고 결제하는 것을 같은 단위(트랜잭션)로 묶을 필욯가 있다. 구매를 했는데 결제가 안 되면 문제가 생길 수밖에 없다.

트랜잭션이 성공해서 커밋을 하면 한번에 2개의 결과가 저장되고, 실패하면 롤백이 돼서 주문과 결제 모두 취소돼야 한다.

*ACID
automicity 원자성(all or nothing)

송금을 했으면 상대방은 돈을 받아야 한다. 돈을 송금하는 것은 성공했는데, 받은 것에 실패하면 안된다. 중간에 작업이 중단되지 않도록 보장해야 한다.

consistency 일관성

트랜잭션이 성공하면 언제나 일관성있는 상태를 유지해야 한다.

isolation 독립성

트랜잭션이 수행되면 다른 트랜잭션의 연산작업이 끼어들어선 안 된다.

@Transactional은 언제써야할까?

쓰기 지연

public class Accommodation {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)

IDENTITY 전략의 경우 JPA의 쓰기지연이 안될 수 있는데요.
이 부분 인지 하고 계신걸까요?
모르셨다면 왜 쓰기지연이 안되는 상황이 있는지? 부터 접근을 해보시는 게 좋습니다.
자동증가 기본키가 맹목적으로 안좋다기보다는 상황에 따라 더 나은 선택지가 있는 거 같습니다.

**isolation에 대한 내용

https://taetaetae.github.io/2016/10/08/20161008/

Transaction 어노테이션 안에 격리 수준에 대해서 설정을 할 수 가 있다.

MultiPart

MultipartFile의 경우 서버가 파일을 메모리에 올려야 하기 때문에
상황에 따라 JVM 메모리를 초과해서 자바 애플리케이션에서 OOM이 발생할 수도 있는 문제도 있습니다.
이런 부분도 같이 고려해봐주시면 좋을 거 같아요.

profile
답을 찾기 위해서 노력하는 사람

0개의 댓글