들어가며
GitHub저장소
구현 기능들
코드 리뷰
후기
📆기간 : 2023.05.22 ~ 2023.06.05
들어가며
이번에는 프론트엔드 크루 에디, 루루랑 백엔드 크루 달리,홍고랑 같이 협업을 진행하게 되었다. 다들 너무 좋은 크루들이여서 협업을 막힘없이 잘 진행할 수 있었다. step1과 step2로 나뉘어졌는데 이번 미션에서 백엔드 크루들은 step1과정에서 쉘 스크립트를 작성하고 db를 설계하고 할 일이 별로 없었다. 그래서 나는 그 사이에 도커에 대해서 학습 및 실제로 사용을 위한 환경 구축을 진행하였다. 다만 ec2 의 메모리가 적고, 우테코에서 db서버를 제공해주었기 때문에 후에는 크게 사용할 일이 없어서 사용하지 않았다. 😂 그 뒤로 db서버를 사용해서 구현을 하였다.
GitHub저장소
구현 기능
먼저 erd는 이런식으로 구현하였다. 가장 고민이 되었던 부분은 order_coupon이였다. order 취소 시 쿠폰을 다시 사용 가능하도록 해야했기에 이렇게 구현하였다.
주문
주문기능으로는 주문저장, 주문 전체조회, 주문 상세조회, 주문 취소, 주문 확정 기능이 있다.
주문 저장은 사용자가 주문한 것에 대한 상품 id list, 쿠폰 id들을 request로 받아 저장한다.
주문 전체 조회는 사용자 id에 해당하는 모든 주문을 조회한다.
주문 상세 조회는 조회와 비슷하지만 사용한 쿠폰에 대한 내역도 조회해준다.
주문 확정 기능은 사용자의 주문을 확정하게 될 경우 쿠폰을 제공해주기 위함이다. 확정을 누름으로써 주문 취소가 불가하며 쿠폰을 제공하도록 하였다.
쿠폰
쿠폰은 추가, 조회, 전체 조회로 구현하였다.
쿠폰을 추가하는 것은 사용자가 추가하는 쿠폰을 사용자 소유로 만들어 주는 것을 의미한다.
쿠폰을 조회하는 것은 사용자가 소유하고 있는 쿠폰에 대한 정보만 제공한다.
쿠폰 전체 조회는 사용자에게 쿠폰을 제공하면서 이미 발급 되었는지 여부를 함께 제공한다.
레포지토리 분리
먼저 나는 레포지토리를 분리하였다. 위에 자세한 정리를 해놓았다.
분리한 이유는 지금까지 Dao를 이용하여 영속성 계층과 서비스 계층 간 소통을 해왔다. 이렇다 보니 서비스 단에서 해야할 일이 많았다. 코드가 길어지고 가독성이 떨어지는 것 같아 Repository를 통해 그 역할을 분리해서 사용해보았다.
Repository의 구현체는 ~~~RepsitoryImpl 하나 뿐인데 인터페이스를 사용한 이유는 추상화를 위해서 였다. 구현을 구현체에게 위임함으로써 결합도를 낮추려 하였다. 구현체가 하나뿐인데 인터페이스를 사용한 이유는 domain 계층에 Repository 인터페이스를 두고 영속성 계층에 Repository 구현체를 두면서 계층간 분리를 하는 방식을 위해서 사용하였다.
코드리뷰
누구를 위한 API 명세인가 ?
우리 팀은 이런식으로 api 명세를 작성하였다. 협업을 진행하면서 프론트엔드 크루와 백엔드 크루 간에 서로 공통적으로 사용하기위해 작성하였다. 하지만 이 api 명세는 부족한 점이 많았다.
개발자를 위해서 요청, 응답 파라미터, 타입, 필수 여부, 부가적인 설명, 실패 시 어떤 응답을 제공하는 지 등 을 개발자를 위해서 제공해야함을 배웠다!
서버와 클라이언트 어디서 기능을 처리하는 것이 좋을까 ?
프로젝트를 진행을 하면서 처음에는 백엔드, 프론트엔드 크루 모두 자신들이 기능을 처리하겠다! 라는 의견을 가지고 있었다. 하지만 진행을 하다 보니 그럴 필요가 없어졌다. 이렇게 진행하면서 내린 결론은 클라이언트 측에서 장바구니에서 수량 변경에 따른 동적 계산이나, 단순하게 가격을 보여주기 위한 계산 로직과 같은 것을 처리하고 서버 측에서는 주문이 저장될때 이를 유효성 검사, 데이터 처리를 진행한다고 결론 내렸다. 서버 측에서 수량 변경에 따른 계산, 가격 조회 등을 할 수는 있지만 그에 따른 API 호출이 더 발생한다고 생각되어 나누게되었다. ( 수량 update 시 가격이 변동되어야 할 때 수량을 증감시키면 수량 update api 호출 , 가격 조회 api 호출 )
프로그램의 성격에 따라 상황에 따라 어디다 두는 것이 조금 더 효율적일지, 조금 더 안전할지 등을 고민하면서 조율해나가면 좋을 것 같다. 다른 웹사이트를 방문해서 인터넷 도구를 이용해 어떤 것을 수정할 때 BE를 호출하는지를 보는 것도 좋다!
네이밍 및 파라미터 통일
진행하면서 네이밍과 파라미터가 통일성있는 모습을 보이지 못했다. 나도 모르게 헷갈렸던 문제였다. 메소드 명명 기준으로써는 find By ~, delete By ~ 와 같은 네이밍으로 통일하였고, 메소드 명에 따라 파라미터 순서도 통일성있게 사용하였다.
또한 repository의 메소드를 사용시 어떤 것은 id, 어떤 것은 domain을 반환하였다. 나는 이에대해 상관없다고 사용하였고 필요없는 데이터 까지 넘긴다는 단점이 있는 것 같았다. 일관성때문에 필요없는 데이터가 계층을 넘어간다고 생각하였다. 만약 메소드에서 예를들어 saveAndReturnId 와 같은 방식으로 메소드명이 구분되어있다면 상관 없을 것이다. 메소드 명에서 명시적으로 나타내지 않는다면 비즈니스 로직을 repository가 알아야한다. 그래서 변경하였다.
NULL 지양
쿠폰을 잘못 선택했을 경우 null을 통해 검증을 하게 되었는데 null을 최대한 지양해야된다고 생각한다. 다만 제이미는 null을 무조건적으로 지양해야하는 이유가 있을까? 라는 생각이었다. 필요한 상황에서 지양해야된다고 생각한다고 하였다.
내가 생각한 null을 지양해야되는 이유로써는 NPE 발생시 추적과 디버깅이 어렵다고 생각한다.(당장 혼자해도 어려운데...) 혼자 작성하는 코드라면 null 값에 대해 명확하게 알고 사용할 수 있겠지만 타 개발자가 본다면 이를 이해하기 어려울 것 같았다. 또한 개인적으로 null 값 자체가 불완전한(?) 값이라고 생각하고 있어서 코드 단계에서는 최대한 옵셔널이나 빈객체를 사용해야된다고 생각하고 있다..
물론 필요한 상황에서만 지양을 한다는 것이 좋겠지만 그 필요한 상황에서도 위에와 같은 문제점이 발생할 수 있다고 생각한다! 이에 대해서는 사람들마다 다를 수 있지만 결국 상황에 따라 같이 협업하는 페어, 팀원들과 맞춰가면 될 것 같다.
주문 취소 Soft Delete
일단 지금 구조에서는 주문 취소된 건에 대해서는 조회하는 로직이 없었기 때문에 삭제해도 된다고 생각하였다. 나중에 주문취소한 내역또한 출력을 하고자 한다면 isDeleted, available과 같은 boolean 컬럼을 생성하여 true, false로 처리를 할 것 같다.
아예 삭제, soft delete, 삭제하지 않는다에 대한 선택지 장단점으로는
1번에 대한 방법은 현재 진행했던 방법이고 장점은 깔끔하게 취소 시 삭제, 취소 x 시 존재 라고 단언할 수 있을 것 같다. 단점으로는 조회하는 로직이 추가될 경우 문제가 발생하는 단점이 있다.
2번에 대한 방법 장점은 주문취소 조회 시 대응이 가능할 수 있다. 단점으로는 새로운 column을 추가해야하다보니 이 자체로 단점인 것 같다.
3번에 대한 방법은 주문 취소라는 기능이 존재할 때는 필요하지 않는 것 같다!
JOIN 지양
처음 나의 의견은 join을 지양하는 것이었다. 이렇게 진행한 이유는 테이블이 커지게 되면 join 시 성능 문제가 발생할 것 같아서였다!
그러다보니 연결테이블에 대해서도 모두 dao와 repository를 두고 있는데, join을 사용한다면 굳이 연결 테이블에 대한 dao, repository가 필요 없어질 것 같았다. 다만 이 생각은 바로 접어버리게 되었다 . .
정확하게 테이블이 어느정도 규모일때 성능 문제가 발생한다 라고 알 수는 없지만 많은 table을 인덱싱 없이 조인하게 되면 O(nm....)이런식으로 기하급수적으로 늘어날 것 같다. 두개의 테이블만 join을 한다면 수백만개의 로우정도 되야 성능이 저하될 것 같았고 이번 미션에서는 그것에 대한 고려를 할 필요가 전혀 없는 것 같다고 느꼈다.🫠
지금은 dao에 repository도 생성이 되었지만, 무조건적으로 생기는 것은 아닌 것 같다. Repository는 다른 도메인들과 연관이 있고 함께 변경될 필요가 있을 때, 이들의 시작 도메인에 대해서만 있으면 될 것 같다.
예를 들어 현재 OrderService에서 order를 저장하고자 할 때, orderProduct가 저장이 되기도 하고, orderCoupon이 생성되기도 한다. 이럴 경우 orderRepository에서 처리를 하면 될 것 같다. 이들을 repository단에서 처리를 함으로써 order저장에 대한 로직이 변경되었을 때 변경 여파가 service까지 오지 않으며, order가 product, coupon에 의존적인 것을 드러내지 않도록 한다!
지금처럼 service에서 각각을 처리해준다면 repository가 의미가 없어진다고 생각이 들었고 그에따라 조금 코드 수정을 진행하였다!
지금까지 레벨2과정을 겪으면서 연결테이블은 거의 없었고 join을 사용할 일이 없었다. 그래서 지양해 왔었는데 이번 미션을 겪으면서 연결테이블이 많아졌고 스키마의 구조가 복잡해졌다. 이럴 경우 join을 사용하는 것이 좋다고 생각하게 되었다.
Join을 사용하지 않는다면 쿼리작성이 간단하고, 쉽게 진행할 수 있다. 단일쿼리들의 값을 조립하여 객체를 만드는 형식이기에 쿼리 하나하나를 재사용하기 용이하다는 장점이 있다. 다만 여러번의 쿼리를 날려서 객체를 만들어야한다는 단점도 있다.
Join을 사용한다면 여러 쿼리를 실행하고 결과를 조합하는 대신, 단일 쿼리로 모든 데이터를 한 번에 처리할 수 있다는 장점이 있는 것 같다. 단점으로는 3-i에서 말했던 이유가 있을 수 있을 것 같은데 지금 단계에서는 크게 고려할 필요없다고 생각된다..!
EMPTY 사용
public static Coupon EMPTY() {
return new Coupon(
null,
"EMPTY_COUPON",
new EmptyDiscount()
, 0, 0, 0
);
}
먼저 Null 을 사용한다면 직관적으로 아무것도 없음을 알 수 있어 이해하기 쉬울 것 같고 따로 객체를 생성할 필요가 없다.
다만 이럴 경우 호출하는 쪽에서 Null 체크를 항상 수행해야하며 없을경우 NPE가 발생할 여지가 있다.
EMPTY를 사용하는 것은 호출 시 Null체크를 할 필요가 없어 Null을 사용하는 것보다는 안전한 것 같다. 다만 아무것도 아닌 값에 대해 객체를 생성한다는 비효율적인 단점이 있을 것 같다.
내가 EMPTY를 사용했던 것으로는 주문에 쿠폰이 적용되어 있지 않을때 쿠폰 값이 Null 이라면 그 쿠폰을 항상 Null 체크를 수행해야 할 것 같아서 상대적으로 안전하다고 생각하는 EMPTY를 사용하였다.
Mock을 사용하지 않는 테스트
나는 Mock 가짜 객체를 사용하는 테스트를 지양한다. 테스트의 목적이 첫째, 코드의 기능적인 측면을 확인하고 검증하는 것이며, 둘째, 코드의 동작과 실행 흐름을 확인하는 것이라고 생각한다. mock을 사용한다면 후자일 것이고 사용하지 않는다면 전자라고 생각한다.
테스트에서 중요하다고 생각되는 부분은 기능적인 측면을 확인하고 검증하는 것이다. 특정 입력에 대해 코드가 예상한 결과를 반환하는지, 예외 상황에서 적절한 예외를 던지는지에 대해 검증해야한다고 생각한다. 코드의 로직이 실제 데이터와 의존성을 기반으로 정상적으로 작동하는지를 확인할 수 있기 때문에 mock을 사용하지 않고 테스트를 진행하는 것이 더 좋지 않나? 라고 생각하게 되었다.
물론 실제 db하고 상호작용 하거나, 외부 의존성 때문에 mock을 사용하지 않을 경우 단점도 있을 것 같다. 하지만 이번 경우에서는 굳이 mock을 사용할 필요가 없다고 생각해서 사용하지 않았다. 꼭 필요할 경우에만 사용하면 된다고 생각한다.
후기
먼저 이번 미션에서 제이미가 리뷰어였기에 양질의 리뷰를 받을 수 있어서 너무 좋았다. 그리고 페어였던 루루,에디,홍고,달리 도 너무 좋아서 재밌는 협업 경험이 되었다. 이번 협업에서는 협업하는 과정에서 어떤 것을 중점적으로 생각해야 될지에 대해 알게 되었다. 부족한 부분도 많이 알게 되었다. 프론트, 백간 의견차이 구현을 함에 있어서 api 명세 등등 . . 이 모든 것을 종합적으로 생각하고 남은 레벨3,4를 향해 가야겠다고 결심했다.
마지막 미션이었던 만큼 아쉬웠던 부분이 많았다. 결과적으로는 잘 마무리를 지어서 만족스럽다.
'우아한테크코스' 카테고리의 다른 글
[우아한테크코스 5기] 레벨3 1, 2 주차 회고 (3) | 2023.07.09 |
---|---|
[우아한테크코스 5기] 레벨2 레벨인터뷰 회고 (0) | 2023.06.13 |
[우아한테크코스 5기] 지하철 2단계 학습로그 (2) | 2023.05.28 |
[우아한테크코스 5기] 지하철 1단계 학습로그 (1) | 2023.05.21 |
[우아한테크코스 5기] 장바구니 2단계 학습 로그 (1) | 2023.05.07 |