코드스쿼드 4 - (ResponseEntity, Auto_incremnet의 위험성)

Alex·2024년 7월 2일
0

리팩토링

목록 보기
5/17
    public enum ImageStatus {
        ACTIVE, DELETED
    }
    

해당 엔티티안에서만 사용할꺼면 private으로 변경하는게 좋은거 같아요. 근대 이거는 service layer에서도 사용될거라 별도 클래스로 하는게 더 괜찮은거 같습니다.

이런 리뷰를 봤다.

@Entity
@Getter
@NoArgsConstructor
public class ServiceCharge {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private ServiceType serviceType;
    @Column(nullable = false)
    private BigDecimal fee;
    private Integer intervalDays;

    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "accommodation_id")
    private Accommodation accommodation;


    public ServiceCharge(ServiceType serviceType, BigDecimal fee, Integer intervalDays) {
        this.serviceType = serviceType;
        this.fee = fee;
        this.intervalDays = intervalDays;
    }

    public BigDecimal calculateFee(int days) {
        BigDecimal daysBigDecimal = BigDecimal.valueOf(days);
        BigDecimal weeks = daysBigDecimal.divide(BigDecimal.valueOf(7), 2, RoundingMode.HALF_UP);
        //소수점 2이하 자리까지 반올림
        BigDecimal roundedWeeks = weeks.setScale(0, RoundingMode.HALF_UP);

        return roundedWeeks.multiply(fee);
    }
}


public enum ServiceType {

    CLEANING
}

이렇게 돼 있었는데
이 enum 타입을 사용하는 게 entity 클래스밖에 없어서 private으로 수정해도 될 거 같다. 굳이 다른 곳에서 접근하게 놔둘 필요는 없을 거 같다.

컨트롤러 반환 타입

@PostMapping("/register")
    public ResponseEntity<?> registerMember(@Valid @RequestBody MemberRegistration memberRegistration) {
    

응답으로 제네릭의 ?가 가면 FE랑 통신할때 나중에 문제될 여지가 있을거 같아요. 제네릭 말고 특정 객체를 반환하는게 더 좋습니다.
ResponseEntity도 ApiResponse와 같이 별도로 만드는게 괜찮을거 같아요

타입을 특정해서 보내주는 게 좋다고 한다. 그래야 실수의 여지를 줄일 수 있을 거 같다.


public class ApiResponse<T> {
    private ApiResult result;
    private T data;
    private Exception error;
    
    public ApiResponse(ApiResult result, T data) {
        this.result = result;
        this.data = data;
        this.error = null;
    }

    public ApiResponse(ApiResult result, Exception error) {
        this.result = result;
        this.error = error;
    }

    static <T> ApiResponse<T> success(T data) {
        return new  ApiResponse(ApiResult.SUCCESS, data);
    }
    
    static <T> ApiResponse error(Exception error) {
        return new  ApiResponse(ApiResult.SUCCESS, error);
    }
    
    private enum ApiResult {
        SUCCESS, ERROR
    }
}

반환 타입을 ResponseEntity대신 이렇게 만들어보라는 피드백도 있었다. 왜 그런걸까?

서치를 해봤다.

Spring : ResponseEntity를 사용해야 하는 이유

Restful api를 만드는 이유는 Client side를 정형화된 플랫폼이 아닌 모바일, PC, 어플리케이션 등 플랫폼에 제약을 두지 않게 만들기 위해서다.

각각의 플랫폼에 맞춰서 Serer Side를 일일이 만드는 것은 비효율적인 탓이다.

이 때문에 우리는 메시지 기반, xml, json 처럼 cline에서 바로 객체로 치환가능한 데이터 통신을 지향하게 된 것이다.

이에 따라 HTTP의 표준 규약을 지켜서 API를 만드는 것이 필요해졌다.

Restful api를 개발할 때는 HTTP의 Response 규약을 지치지 않고 본인들이 만든 Json 규약으로 응답하는 경우가 많다고 한다.
만약, 표준을 지키지 않으면 Client side가 정형화되지 않은 환경에서 개발을 해야 하기 때문에 개발 속도가 느려진다.

가령, HTTP Status 코드를 제대로 응답하지 않게 된다고 해보자. 그러면, 클라이언트에서 별도의 방어 코드를 넣어야 한다고 한다(방어코드가 뭔지는 잘 모르겠다...)

그래서, Contoller에서 결과를 반환할 때는 ResponseEntity객체를 이용해서 규약에 맞는 Http Response를 보내야 한다.

이런 규약을 지키기 위해서 만든 ResponseEntity가 있는데 굳이 ApiResponse를 만들어서 사용해야할까? 아직 그 필요성이 와닿지 않아서 우선은 ResponseEntity를 사용하기로 했다.

db레벨에서 재사용성


    @Override
    public Double findCommentRatingAvgByStayId(Long stayId) {
        String jpql = "SELECT AVG(sc.rating) FROM StayComment sc WHERE sc.stay.id = :stayId AND sc.status = :status";

        return (Double) em.createQuery(jpql)
                .setParameter("stayId", stayId)
                .setParameter("status", CommentStatus.ACTIVE)
                .getSingleResult();
    }
    

db랑 직접 통신하는 low level에서는 CommentStatus.ACTIVE를 하드코딩하면 재사용성이 떨어집니다. 따라서 인자를 넘기는게 재사용성이 높습니다.

의존성


public String getToken(HttpServletRequest request) {

HttpServletRequest에 의존적이라 호출하는쪽에서 아래코드를 사용해서 넘겨주는게 더 괜찮은거 같아요

String token = request.getHeader("Authorization");
        if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
            return token.substring(7);
        }
        

Auto_increment 사용

ID 생성의 역할을 AUTO_INCREMENT을 통해 사용하시는군요.
만약, 분산 환경에서 여러 인스턴스가 동시에 데이터를 삽입하는 경우 어떤일이 벌어질까요? 만약 문제가 있다면 어떤 방법으로 해소할 수 있을까요?

이 피드백을 보고서 공부를 해봤다.

분산환경에서 DB 기본키(PK)는 어떤 ID 생성 전략으로 만들어야할까? (UUID,ULID,TSID...)

별 생각없이 자동증가를 썼는데 이런 복잡한 문제들이 발생한다는 게 신기하기도 하고... 개발이 어렵다는 게 느껴진다.

이를 해결하기 위해서는

1)UUID를 쓸 수 있다.

128비트짜리 수로, 중복 가능성이 매우 낮다. 서버간 조율 없이 독립적으로 생성이 가능하다.

다만, 용량이 크기 때문에 대규모 테이블에서 저장공간을 상당히 차지할 수 있다. 시간순으로 정렬되지도 않는다.

게다가 MYSQL 인덱스는 B+Tree 구조로 순차성에 강한데, UUID는 랜덤이므로 성능 저하가 발생할 수 있다고 한다.

2)트위터 스노우플레이크 방식


3) TSID(Time-Sorted Unique Identifier) 방식


사용할 떄는 의존성을 추가하고

public class TestEntity {

    @Id @Tsid
    private Long id;

    private String title;
}

이렇게 넣어주면 된다고 한다.

서비스 범위

주소(Address)를 전체 하나로 저장할 지, 계층 구조에 따라 나눌지에 대해 고민했습니다. 현재는 Address를 임베디드 타입으로 정하고, 주소를 앞에서부터 순서대로 3개의 부분으로 나누어서 저장하기로 했습니다. 현재 방식이 괜찮은 지 여쭤보고 싶습니다!

개인적으로는 복잡한 주소 체계를 값 객체로 명시적으로 구조화 시킨 것은 코드 가독성을 향상 시켜서 좋은 거 같아요.
다만, 한 가지 짚고 넘어가야할 것은 해당 서비스가 국내 서비스를 타겟팅 할거고, 해외 서비스는 타겟팅 하지 않는다.라는게 확정적이어야 유효한 설계일 것 같습니다.
만약, 에어비앤비처럼 글로벌 환경도 지원할 서비스라면 주소 체계를 좀 더 유연하게 대응할 수 있는 필드로 개선이 필요할 것 같아요. (각 나라별 주소체계가 조금씩 다르기 때문이에요.)

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

0개의 댓글