Browse Author

gemini

개인취향 JPA 사용기 3편 – Scalable Service

안녕하세요? gemini 입니다.

제가 속한 가자고 팀은 매 2주간의 스프린트 마다 추가 기능을 배포하고 있습니다.
아직 오래전 급하게 만든 코드 등 숙제들이 많지만, 많은 부분이 변하여 순조롭게 기능 확장 중입니다.

이번에 제가 공유하려는 내용은 서비스의 기능을 지속적으로 확장시키는 경험 속에서 느꼈던 점을 공유해보겠습니다.
특히 이번 글은 더 개인 취향에 의한 주관적 생각이오니 많은 의견 주시면 좋을 것 같습니다.

먼저 간단한 상황을 예제로 들어보겠습니다.

기획팀에서 아래와 같은 기능 개발 요청이 왔습니다.
1. 유저는 장바구니에 새로운 상품을 추가할 수 있어야 한다.
2. 유저가 장바구니에 담았던 누적 수량, 금액을 저장하고 있어야 한다.

자, 요구 사항을 위해 Business Logic 을 담을 CartService 를 만들어 보겠습니다.

벌써 다 만들었습니다! Cart 추가할 때에 User 를 불러와서 수량 과 금액 을 갱신 시켜 줍니다.

물론 동작에는 이상이 없지만 CartService 에서 User 를 가져와서 제어하는 형태가 제 취향은 아닌 것 같습니다.
테스트 코드 작성하기도 조금 까다로워 보이네요.

일단 제 취향대로 변경하기 위해 User 의 대한 처리를 UserService 가 수행하도록 변경해보겠습니다.

오우, 좋은 것 같습니다. 테스트 코드도 훨씬 작성하기 쉽겠네요
CartService 는 장바구니 저장만 해주고, User 의 대한 내용은 UserService 에게 맡깁니다.

그런데.. 또 다른 요구 사항으로 인해 UserService 에서도 CartService 가 필요한 상황이 발생합니다.
요구 사항을 반영하기 위해 UserService 에서 CartService 를 사용하여 작업을 진행해봅니다.

엇! 구현을 모두 끝내고 서버를 띄워보니 에러가 발생합니다.
Screen Shot 2016-09-19 at 3.02.57 AM
다들 추측하셨겠지만 UserSerivceCartService 가 상호 참조를 하고 있습니다.
이 문제는 다양한 방법으로 해결할 수 있습니다. 일단은 CartService 쪽 의 의존성을 끊어서 해결하겠습니다.

결국은 CartServiceUserRepository 를 통하여 직접 User 를 가져오고 이를 처리하게 되었습니다.
정상 동작할 것이고, 문제 될 것은 당연히 없습니다.

그렇지만 역시 제 취향은 아니네요.
저의 취향대로 Business Object 를 만들어 핵심 로직을 처리하도록 수정해보겠습니다.

이제 CartServiceUserInfoUpdater 를 사용하도록 수정하겠습니다.

이제 CartServiceUserService 와 관계가 전혀 없어졌습니다. 그리고 CartService 에서는 User 에 관하여 어떤 기능만을 사용하는지 더욱 명확해진 것 같네요.

또한 아주 약한 결합을 가지고 있기에 테스트를 작성하기가 아주 편하고 테스트도 설명하는 바가 명확합니다.

제 취향을 강조하기 위해 빡빡한 예를 들어보았습니다. 적절한 예제였는지는 잘 모르겠습니다, 조금 억지가 있어도 양해 부탁드립니다.

꾸준한 기능 확장으로 인해 Service 들에는 수십 개의 Method 가 생길지 모릅니다.
그런 상황에서 Service 들끼리 엉켜 있다면 서로를 어떤 의도로 쓰는지, 또 다른 곳에서는 어떻게 쓰이는지 예측하기가 힘들어지기 때문에 수정하기 힘들어집니다.

심한 경우에는 AService 안에서 B, C, D, E, F, G, H, I, J 서비스들을 가지고 있을 수도 있겠지요.

결론적으로 제가 공유하고 싶었던 내용은 초기에 Service 를 만들고 기능을 계속 확장해가며 너무 커져버린 Service 로 인하여 생기는 문제, 그로 인하여 점점 더 확장해나가기 힘들어지는 상황에 대한 경험 그리고 그것을 개선하기 위한 고민과 개선해본 경험을 공유하고 싶었습니다.

한 Service 가 많은 의존성을 가지고 있고 많은 일을 하고 있다면, 그 Service 가 너무 많은 책임을 지고 있다는 것입니다.

의존성과 일이 많으면 테스트하기가 까다롭고, 테스트하기가 까다로우면 확장하는데 자유롭지 못 합니다.

끝없이 요구되는 추가 기능과 버그 수정을 감당하며 문제없이 배포하기 위해서는
모든 객체들의 역할이 명확하고 테스트하기 쉬운 구조를 만들어서 객체들이 유연한 형태라면
당연하게도 확장에 유리할 것입니다.

부족한글읽어주셔서 감사합니다. 많은 생각들을 공유해주시면 감사하겠습니다. geminikims@gmail.com

개인취향 JPA 사용기 2편 – Entity with Getter,Setter and Test

안녕하세요? gemini 입니다.

지난 1편에서는 기본적인 Spring Boot + Gradle + JPA 설정을 해보고, 기본적인 Entity 를 생성해봤습니다.

오늘은 Entity 의 Getter, Setter 그리고 Test 에 대한 제 개인적인 견해와 활용방법을 공유하려 합니다.

우선 1편에서 다룬 User Entity 를 보겠습니다.

name, phone 필드는 Getter, Setter 를 모두 가지고 있습니다만, id 필드는 Getter 만 존재합니다.

Setter 를 만들지 않은 이유는 id 필드의 수동적 제어를 막기 위함입니다. 애초에 id 필드를 제어 불가로 만들어 혹시 모르는 실수를 막는 것입니다.

또한 기존 코드는 lombok 을 사용하지 않았습니다만.
제 취향대로 User Entity 에 iombok 을 적용해보겠습니다.

우선 저는 다른 기능을 사용하지 않고 @Getter Annotation 만 사용하였습니다,
또한 User Entity 에서 Setter 를 모두 제거하였습니다.

Setter 를 삭제한 이유는 이렇습니다. JPA 는 Transaction 안에서 Entity 의 변경사항을 감지히여 UPDATE SQL을 생성합니다.

즉 Setter 가 Update 기능을 수행하고 있습니다. 이런 상황에서 무분별하게 Setter 를 쓰게 된다면,
예측하지 못한 필드가 Update 가 될 때 Setter 를 일일이 체크해야 합니다.

그럼 Setter 없이 어떻게 데이터 수정을 할까요? 수정 된 User Entity 를 보겠습니다.

updateInfo Method 를 통해서 수정이 가능해졌습니다 어떻게 보면 Setter 가 더 유연해 보일 수도 있습니다. name 만 수정하고 싶을 수도 있기 때문이죠

모든 Entity Method 를 기능화 시키긴 힘듭니다. 그렇지만 생각 없이 @Setter 를 달고 무분별하게 Setter를 사용하는 것은 지양하는 게 좋다고 생각합니다.

이제 앞으로 추가될 모든 Entity의 Primary Key 를 id 필드로 제한하고 싶습니다.

여러 이유가 있지만 기본적으론 PK 를 변경해야 하는 상황이 왔을 때 유연하게 대처하고 싶기 때문입니다.

JPA 는 @MappedSuperclass Annotation으로 Parent Entity Class 를 지원합니다.

그럼, 모든 Entity 의 부모가 될 BaseEntity 를 작성하겠습니다.

추가적으로 모든 Entity 의 생성시간, 수정시간을 기록 할 수 있게 @PrePersist, @PreUpdate Annotation 을 사용했습니다.

이제 User Entity 가 BaseEntity 를 상속받게 구현하겠습니다.

변경된 User 입니다. 자식 Entity 쪽에서는 id 필드가 사라졌으나.

여전히 Id 는 외부 수정이 불가능하고 오직 Persist 시에만 생성됩니다.

다른 팀원들이 BaseEntity를 상속받는 규칙을 지키면 모든 Entity 는 id 필드를 Primary Key 를 가진 형태가 될 것입니다.

자 그럼 조금 더 제 취향에 맞게 변경되었으니, User Entity에 대하여 단위 테스트를 작성해봅시다.

정상적으로 기능이 동작하는 것을 볼 수 있습니다.

위의 테스트는 단순하지만 연관관계가 엮인테스트 또는 id 필드가 반드시 있도록 mocking 해야 하는데
id 필드를 어떻게 설정할 것인가? 이제 와서 Setter 를 추가할 것인가? 아니면 테스트를 하지 않을 것인가?

그럼 이제 test Module 에 아래와 같은 클래스를 만듭니다.

지저분 해 보이기도 하고 그냥 Setter 만들고 싶어질 수도 있습니다. 자 그래도 만들었으니 사용해보겠습니다.

이제 테스트 시에만 id 가 존재하는 Entity 생성이 가능합니다.

저의 경우는 테스트 코드 작성 시 Builder 를 만들어서 사용하는 것을 지향하기에 Builder 내부에서 MockEntity 을 사용할 것 같습니다.

굳이 이렇게까지 id 의 Setter 를 제한할 필요는 없을 수도 있습니다. Setter 쓰는 곳 찾는 게 어렵진 않습니다.

그럼에도 제 생각엔 객체의 행위들은 의미가 있어야 하고, 낭비적 코드를 줄여야합니다.
코드를 간결하고 유의미하게 만드는 것이 가장 중요하다고 생각합니다.

어떤 객체는 모든 필드에 대한 Setter 가 존재해야 할 수도 있습니다.
중요한 것은 무작정 @Setter Annotation 부터 선언하지 않는 것이라고 생각합니다.

두서 없는글 읽어주셔서 감사합니다. 피드백은 언제나 환영입니다. geminikims@gmail.com

**해당 프로젝트 소스는 공유 예정입니다.

– 개인취향 JPA 사용기 1편

개인취향 JPA 사용기 1편 – SpringBoot + JPA + Gradle

안녕하세요? 가자고 개발팀 막내를 맡고 있는 gemini 입니다.

저희 가자고 서비스는 Spring Boot + JPA/Hibernate 기반으로 개발이 되어있습니다.
그리고 개인적으로는 JPA를 쓴지는 약 2년 되어갑니다만, 아직 많이 부족합니다.

팀 내부에서는 JPA에 관하여 여러 얘기가 오고 갑니다만, 저는 ORM 옹호파를 맡고 있습니다.
내부적으로 문제 발생 시 자주 JPA를 의심합니다. 그러나 이것은 JPA를 아직 성숙하게 다루지 못하기 때문이라 생각합니다.

물론 러닝커브가 존재하는 것은 맞기에 팀원들이 JPA 스터디에 힘을 쏟을지, 빠르게 걷어낼지는 팀의 선택입니다.
개인적으로 ORM에 관심이 있어서 가자고를 JPA로 개발해가며 느낀 점 + 제 개인의 취향대로 JPA를 지금 보다 더 올바르게 사용해보려 합니다.

먼저 오늘은 Spring Boot + JPA + Gradle 프로젝트 설정을 해볼 것입니다.

프로젝트 생성

Spring Boot 프로젝트 생성을 진행합니다.
Screen Shot 2016-08-24 at 1.01.50 AM

프로젝트 설정

프로젝트 설명은 편하게 작성합니다. 저는 개인적으로 Maven 보다는 Gradle 을 선호합니다.
각각 장단점이 존재합니다만, 해당 내용은 추후 다른 포스팅으로 정리해보겠습니다.
Screen Shot 2016-08-24 at 1.40.55 AM

초기 의존성 설정

프로젝트 생성 후 필요에 따라 추가하면 되기 때문에 무시하셔도 됩니다만, 전 그냥 눈에 들어오는 것만 체크하였습니다.
Screen Shot 2016-08-24 at 1.04.25 AM

자 이제 프로젝트가 생성되었습니다. 시작이 반인데 벌써 끝나갑니다.

프로젝트 생성 후 build.gradle 을 확인해봅니다. 조금 길고 뭐가 필요한지도 모르겠습니다.
그래서 저는 아래와 같이 수정하였습니다. 모르는 건 일단 지우고 필요할 때 추가하면 될 것 같습니다.

필요에 따라 부가적인 의존성을 추가합니다, Boot 1.4가 릴리즈 되고 Hibernate 버전이 5.0.9.Final 으로 올라왔습니다.
Boot 1.3.x로 설정할 때에는 Hibernate 버전을 강제로 올려줬습니다만, 버전 명시를 생각 없이 늘리다 보면 Boot를 사용한 의미가 퇴색되는 것 같기에 가능한 지양합니다.

application.properties 을 설정합니다, 일단 저는 최소한의 설정을 넣어봤습니다.

Spring Context가 잘 load 되는지 contextLoads 테스트를 돌려봅시다.

Screen Shot 2016-08-24 at 1.53.44 AM

별문제 없이 테스트가 통과하였네요. JPA를 올바르게 쓰는 과정에서 테스트가 중요하다 생각합니다.

앞으로 어떤 것을 만들지 잘 모르겠으나, 장바구니고객 객체가 존재한다고 생각해보겠습니다.
대략 이런 형태로 존재하겠지요.

id 필드는 getter 만 만들었는지, lombok은 몰라서 안 쓰는 건지 궁금하실 수도 있으실 겁니다만
해당 내용은 다음 편에 적도록 하겠습니다.

그럼 이제 Boot를 Run 시켜봅니다.
Screen Shot 2016-08-24 at 11.15.26 AM

정상적으로 동작한 것처럼 보이네요, 다행입니다.

충분히 예상 가능하지만 무슨 일이 벌어졌나 DB Tool을 실행시켜 테이블 목록을 한번 확인해봅니다.

객체만 만들었을 뿐인데 상응하는 테이블이 잘 만들어졌습니다.

몇몇 설정들도 제가 원하는 대로 적용된 것 같네요, 다음 편에서는 이러한 JPA 설정과 객체 설계에 대한 생각을 공유해보겠습니다.

피드백은 언제나 환영합니다. geminikims@gmail.com

두서없는 글 읽어주셔서 감사합니다.