Search
Duplicate

2024년 상반기 회고

순서
3
날짜
2024/08/01
사람
상태
Done

신규 프로젝트

작년 겨울에 부정 거래 어드민 사이트를 담당하게 되었습니다. 선물하기, 톡스토어 등 다양한 유저의 패턴을 분석한뒤 룰을 정의하고, 정의한 룰을 기반으로 적발된 데이터를 적재, 검수하는 웹사이트입니다.
신규 프로젝트라서 프로젝트 패키지 구조, DB, 프레임워크, 라이브러리 등 사용한 것에 대한 대략 적인 내용을 정리하도록 하겠습니다.

패키지 구조

패키지 구조는 기본적으로
L cache
L web
L core
L storage
로 구성하였습니다.
처음에는
L domain
L controller
L service
L repostiory
로 생각하였는데, 이렇게 되면 도메인을 분리하기도 어려워지고, 웹에 종속적인 프로젝트로 되서, API 호출할때도 패키지를 새로 구성해야 돼서 유연하지 못하다고 생각하였습니다.
도메인 분리가 어렵다는 것은
ex) Rule 하위에 filter라는 도메인이 있을 수 있는데, 각각의 도메인에 controller, service, repostiory를 만들게 됨으로, 유연하지 못하고 부모와 자식의 도메인을 생성할 수 없었습니다. 때문에 처음 작성한 패키지 내용으로 작성하게 되었습니다.

책임의 분리

web패키지를 따로 구현한 이유는 프로젝트가 꼭 웹에서만 동작하지 않을 것을 가정하였습니다.
web : 만약 core에 web에 관련된 사항들을 넣게 된다면, 프로젝트가 웹에 종속적인 프로젝트로 진행이 되고 추후에 프로젝트를 확장하는 데에 큰 걸림돌이 될 것으로 생각하였습니다.
core : 도메인과 서비스를 넣을 수 있도록 구성하였습니다. 이를 통해서 재사용성을 높이고, 테스트 코드를 작성할때 웹 관련 로직이 필요 없어짐으로 순수하게 테스트를 진행할 수 있을 것으로 생각하였습니다.
storage : 스토리지는 core에 넣을지, 따로 패키지를 분리해서 할지 고민이 많았습니다. 코어에 넣게 되면 프로젝트를 진행할 때, 각각의 도메인에 근처에 위치하기 때문에 다른 사람들이 분석할 때 쉬울 것이라고 생각하였습니다. 하지만 이렇게 되면 추후에 프로젝트가 확장될 때 core의 도메인 하위에 레파지토리에 접근해서 사용하는 사람도 있을 것 같아 맞지 않다고 생각하였습니다.
cache : cache 같은 경우는 Spring 자체 Ehcache, redis 등 다양한 캐시가 존재하다고 생각하며, Ehcache → Redis로 옮겨지는 경우도 많이 봐왔기 때문에 유연성을 위해 따로 분리하였습니다.

Mybatis vs JPA

결론적으로 현재 2개 동시에 사용하고 있습니다.
기존 프로젝트에는 JPA만 썼는데 문제는
1.
모든 사람들이 select 쿼리가 어떻게 나가는지 확인하지 않는다는 것
2.
복잡한 쿼리는 predicate를 사용하면서 join을 하였는데 다중상속을 할 경우 문제점을 찾기 어려웠습니다. 엔티티 객체에는 데이터가 없고 쿼리는 나가는데 어디서 찾아야 될지 명확하지 않았습니다. SQL중심 Mybatis였다면 좀 더 찾기 쉬웠을 것이라고 생각하였습니다.
3.
JPA는 지연로딩을 사용하게되면 DB 커넥션 풀에서 해당하는 트랜잭션을 점유하고 있습니다. 점유를 하고 있다가 해당하는 데이터가 필요로 할 때 사용하는데, 이부분을 신경쓰지 않으면 DB 커넥션이 부족한 경우도 발생 할 수 있습니다.
4.
현제 저희 프로젝트는 JPA에 대해 잘 모르는 사람도 있기 때문에, 연관관계에 대해서 잘 알지 못하고 사용할 경우 사이드 이팩트가 발생될 수 있다고 생각하였습니다.
위 사항을 기반으로 JPA와 Mybatis를 혼용하고 사용하고 있으며 사용하는 기준은 아래와 같이 정의하였습니다.
1.
join이 복잡하게 들어가는 쿼리는 Myabtis를 사용하자
2.
Mybatis와 JPA를 분리해서 사용하자
3.
insert update delete는 JPA를 사용하자
입니다.

Kafka vs API

현재 부정 거래에 대한 적발 데이터는 Kafka로 받고 있는 중입니다.
카프카는 이번 프로젝트를 진행하면서 처음 써봤는데요. 사용하게 된 이유는 근본적으로 제공해 주는 측에서 Kafka로 전달해 준다고 하였지만, 그래도 사용하는 이유는 알고 쓰는 게 좋을 것 같았습니다.
1.
빠른 처리 속도
API 같은 경우 기본적으로 동기방식으로 데이터를 보내고 응답을 받는 구조입니다. 때문에 톡스토어와 선물하기 같은 대용량 데이터 같은 경우 메시지 브로커를 통해서 비동기 방식으로 전달하고, 준 실시간 처리로 빠르게 데이터를 전달하고 처리할 수 있게 하는 게 좋다고 생각하였습니다.
2.
확장성
카프카는 카프카 컨슈머 그룹별로 프로듀서에서 데이터를 가져와 사용할 수 있습니다. 이를 통해서 차후에 ES와 적발 데이터를 적재하는 부분을 나눠서 더 빠르게 데이터를 적재할 수 있게 구현할 것으로 생각하였습니다.
때문에 이를 통해서 현재 저희 프로젝트는 API보다는 Kafka로 처리하는 게 더 맞다 생각하였고, 처음 연동하는 만큼 제대로 알고 적용하고 싶어 아래의 인강을 듣게 되었습니다.

Ehcache vs Redis

회원 API를 적발되는 데이터당 한 번씩 조회할 수는 없었습니다. 이렇게 조회화면 API를 제공해 주는 쪽에 부하가 발생할 수 있기 때문에, 중복된 회원에 대해서는 캐시를 사용하기로 명명하였습니다. 이를 구현하고자 처음에는 Spring Boot의 Ehcache를 사용하려 하였는데, 결론적으로 Redis로 변경하였습니다.
파드간 독립적인 JVM 인스턴를 갖고 있기 때문에 캐쉬를 갱신하거나 새로운 캐쉬가 인입됐을 때 데이터 불일치가 발생할 것으로 보였습니다.
수평 확장의 어려움이 있다고 생각하였습니다. 프로젝트가 커짐에 따라 파드도 많이 생성이 될 텐데, 이때마다 각각의 파드에서의 캐시가 다를 것이고 어떤 파드에 접근하느냐에 따라 데이터 불일치는 더욱 커질 것입니다.
때문에 위와 같은 이유로 Ehcache → redis로 변경하였습니다.

JpaEntity와 도메인의 분리

기존 프로젝는 JpaEntity로 데이터를 불러오고 로직을 처리하고 있었습니다.
이렇게 되니
1.
JPA 엔티티에 비즈니스 로직이 과도하게 포함되면서, 엔티티가 비대해지고 복잡해졌습니다.
2.
JPA에 대한 깊은 이해가 없다면, 비즈니스 로직을 이해하기 어려운 구조가 되어버렸습니다.
3.
비즈니스 로직이 JPA에 강하게 종속되면서, JPA의 특성을 잘 이해하지 못하면 코드의 동작을 예측하거나 디버깅하기가 어려웠습니다.
4.
비즈니스 로직이 JPA 엔티티에 강하게 결합되어 있어 다른 ORM 또는 데이터 접근 방식을 도입하거나 혼용하는 것이 매우 어려웠습니다.
이를 해결하고자 아래와 같은 로직으로 불러오는 로직과 도메인 로직을 분리하였습니다.
@Getter @Entity @Table(name = "member") public class MemberJpaEntity { ... public Member toModel() { return Member.builder() .id(id) .memberId(ruleId) .name(index) .createdAt(createdAt) .updatedAt(updatedAt) .... .build(); } }
Java
복사
위에 언급 한 것 처럼 순수하게 엔티티는 DB를 불러오고 불러온 데이터를 도메인으로 변환하여, 도메인에서 서비스 로직을 수행할 수 있게 구성하였습니다.

테스트 코드 작성

기존 프로젝트 같은 경우 테스트 코드도 너무 적고, 사이드 이팩트도 많이 발생해서, ‘어떻게 하면 좀 더 안정적으로 하면 좋을까?’ 고민이 많았습니다. 때문에 항상 테스트 코드에 대해 구체적으로 알고 싶다는 생각이 많았습니다. 이를 보완하고자 아래의 두 인강을 들었습니다.
아쉬웠던 부분은 테스트를 추가하고 싶은 개발자들의 오답노트를 먼저 듣고, 실용적인 테스트 가이드를 두 번째로 들었던 경험입니다.

테스트를 추가하고 싶은 개발자들의 오답노트같은 경우

Spring 의 DIP DI를 활용해서 FakeImpe을 만들고, 해당하는 Fake 기반으로 Spring boot의 도움이 없이 자체적으로 테스트를 하게 하는 내용이었습니다. 처음 듣는 내용이다 보니 적용해 보고 싶은 마음이 컸고 적용하면서 크게 3가지 문제에 직면하였습니다.
1.
너무 많은 인터페이스
2.
코드를 분석하는 것의 어려움
3.
초기 프로젝트에는 어울리지 않는 오버 스팩
이였습니다.
설명
1.
service, repository, controller를 interface로 만들게 되면 외부에서 내부로만 접근이 가능하겠지만, 해당하는 내용을 알지 못하는 개발자는 데이터 흐름을 찾는 것에 어려움을 느꼈습니다.
2.
프로젝트를 구성하고, 수정하는데 더 오랜 시간이 요했습니다. 로직이 추가되면 컨트롤러, 서비스, 레파지토리에 interface를 추가 해야되고 또 각각의 구현체에도 추가해야 되었습니다.
3.
가장 몸으로 와닿는 것은 소스코드 추적이 어려웠습니다. 기본적으로 소스코드를 따라갈 때 진입하는 단축키나 마우스를 사용하는데 인터페이스로 호출하다 보니 해당하는 로직 접근 자체가 어려웠습니다.
테스트를 추가하고 싶은 개발자들의 오답노트
정말 초기 프로젝트에 큰 도움이 된 인터넷 강의였습니다.
단위 테스트 방법
목빈에 대한 설명
junit 사용법등 다양한 방식과 실무에 어떻게 쓰이는지 알 수 있는 인터넷 강의였습니다.
실용적인 테스트 가이드
개인적인 생각이지만 테스트코드 인강을 듣고 싶다면
실용적인 테스트 가이드를 듣고 추가적으로 테스트를 추가하고 싶은 개발자들의 오답노트를 들으시면, 차후에 DDD 에서 핵사고날 아키텍처를 생각하는데 큰 도움이 될 것으로 생각하였습니다.
오늘은 프로젝트를 구성하면서 경험했던 내용과 어떻게 하면 좋을지에 대한 개인적인 생각을 작성하게 되었습니다. 다음에는 카프카에서 동시성으로 인한 이슈가 발생했었는데, 어떻게 해결하였는지 적도록 하겠습니다. 감사합니다.