코드숨 2주차 - Spring, 그 속으로

beomdrive·2022년 10월 26일
0

멘토링

목록 보기
2/8

“Spring의 편리함”

1주차 과정에서는 순수 Java로 Api를 만들어 보는 과제를 했었다.
순수 Java로 개발할 때는 역할에 따라 책임을 분리 하느라고 많은 클래스를 생성했었다.
또한 각 객체들이 단일 책임을 갖게 하고, 담당 책임에 관련된 행위들은 모두 해당 객체가 수행할 수 있도록 응집도를 높이며 계속해서 리펙토링을 하고 또 했다.

그 결과 아직도 고칠게 많아보이지만 아래와 같은 설계도가 나왔다. (진짜 열심히 낑낑거리면서 고치고 또 고쳤다 😂)
이 이상으로는 야크의 털 깎이가 심해져 시간 투자 대비 얻는 것은 적을 것 같아 여기에서 멈췄다.

2주차에서는 드디어 Spring을 사용해서 Api를 만드는 과제였다.
아마 Test Code를 배우기 전, 워밍업인거 같았다.

2주차 미션을 시작하며, 나는 Spring의 편리함을 이전과는 다르게 느꼈다.

기존에 Spring을 사용했을 때는 오로지 Controller - Service - Repository 쪽만 작업하느라 관심 또한 여기에 머물렀었다.

하지만 이번엔 달랐다.

Spring을 키고 요구 사항을 본 후 클래스를 만드는 순간, 이전에 습관처럼 작성했던 @RequestMapping, @PathVariable, @RequestBody, @Valid 등 모든 것들이 새롭게 느껴졌다.

저번주에는 이렇게 당연하게 사용했던 기능들을 구현하느라 머리가 아팠는데…

다시금 Spring이 왜 자바 애플리케이션을 쉽게 만들 수 있는 프레임워크라고 하는지, 지원해주는 강력한 기능들 덕에 다시 깨닫게 됐다.

“Spring MVC”

이번 미션을 통해서 가장 많이 생각해본 것은 MVC 패턴에 의한 각 객체들의 책임 분리였다.
진짜 가장 많이 생각했다.

Controller에서는 과연 어떤 것만 담당해야될까? 부터 시작한 이 고민은 아래와 같이 퍼졌다.

  • requestDto → Entity 변환은 어디서 하지?
  • Entity → ResponseDto 변환은 어디서 하지?
  • service에서 변환이 이루어져도 되는건가?
  • requestDto를 그대로 service에 넘기는 것이 맞는 것일까?

보기만 해도 벌써 어지럽다. 아직까지도 확신을 못내렸다.
하지만 이것저것 찾아보고, 생각해 본 결론은 “프로젝트 규모에 따라 달라질 수 있다” 이다.

dto를 예시로 생각해보면, 단순한 CRUD도 요구 사항이 복잡하다면 각 CURD 별로 requestDto를 생성해야 될 것이다.
(createTaskRequestDto, updateTaskRequestDto 등의 객체들로 분리를 해주어야 될 수도 있다.)

하지만 규모가 작은 간단한 요구 사항이면 taskRequestDto 하나로 돌려써도 무방할 것이다.

나는 이번주 과제에서는 요구사항이 비교적 간단하여 TaskRequestDto 하나로 요청 데이터를 처리하였고, 전체적인 흐름은 아래와 같은 설계를 해보았다

RequestDto → Entity 변환Service에서 하게 된 이유는 다음과 같다.

  1. DTO는 계층 간 데이터 전달을 위해 사용되는 객체인데, 일반적으로 Repository 레이어는 Entity의 영속성을 관장하는 역할이므로 Service에서 Entity를 넘겨주어야한다.
    따라서 DTO를 Entity로 반환해야되는 곳은 Controller나 Service가 적절하다.

  2. Service 레이어에서는 일반적으로 비지니스 로직을 캡슐화하고, 트랜잭션을 제어한다.
    따라서 트랜잭션 내부에서 Entity를 생성하는 것이 바람직해보였다.

  3. Service 레이어 상단에서 DTO -> Entity 변환 작업을 해줄 시 다른 Controller에서는 해당 Service를 사용할 수 없게 되겠지만, 일반적으로 실무에서는 복잡한 Business Rule로 인해 한 종류의 컨트롤러와 서비스를 1:1로 사용한다.


Entity → ResponseDto 변환Controller에서 하게 된 이유는 다음과 같다.

본래라면 Service에서 RequestDto -> Entity 변환을 담당하니, Entity → ResponseDto 변환 또한 Service에서 하는 것이 역할 응집에 좋은 것 같았다. 또한 Entity를 그대로 반환할 경우 Controller에게 불필요한 정보까지 노출된다는 단점도 인지했다.
하지만 나는 Controller라는 역할을 고민해봤다.
Controller는 애플리케이션 외부와 내부를 경계짓는 역할이다.
외부 요청을 애플리케이션 내부로 해석해 전달하고, 내부에서 반환된 데이터를 래핑하거나 필터링해서 내보내는 역할을 수행한다.
즉 ResponseDto는 외부에 맞게 데이터를 필터링하여 내보내려는 목적이기 때문에, Controller에서 ResponseDto를 생성하는 것이 적절해보였다.
(Service 레이어는 View 계층이 어떤 친구인지에 대해 알 필요가 없다.)

[예시]

Service 레이어를 굳이 HTTP Controller만 사용한다고 생각하면 안될 것 같다.
단편적인 예시로 Controller가 우리가 개발에 익숙한 HTTP 컨트롤러도 있겠지만 프린터기 컨트롤러, 게임기와 통신하는 컨트롤러, 경광등과 연결된 컨트롤러 등 많은 종류의 컨트롤러가 Service 레이어를 사용할 수 있다.
따라서 Controller가 외부 환경에 맞게 데이터를 래핑 or 필터링을 하여 내보내는 역할을 맡는게 적절하다고 생각한다.


“Exception Handling”

이번주에 Spring MVC에 대한 고민 다음으로 제일 많이 시간을 투자한 것이 Exception Handling이다.
정확히 얘기하자면 공통 Exception을 구축하여, 상속을 통해 필요한 Exception을 쉽게 만들어 사용할 수 있게 하며 ExceptionHandler를 사용해 컨트롤러에서 외부로 나가기 전에 원하는 에러 포맷으로 변경해주는 기능을 설계하고 구축하였다.

열심히 뽀짝뽀짝 설계한 흐름도는 아래와 같다.

해당 기능의 자세한 내용은 따로 블로깅을 해보았는데, 다 작성하고 보니 너무 뿌듯했다.


"기술에 정답은 없다"

이번 주 개발을 통해서, 그리고 리뷰어님의 피드백을 통해 가장 많이 느끼는 것은 “기술에 정답은 없다” 이다.
이번 주 리뷰어님(영환님)의 피드백 중 한가지를 소개해볼까 한다.

주제는 Lombok 라이브러리에 대한 내용인데, 이 글을 보고 한동안 멍때렸었다.
“나는 왜 계속 정답을 찾으려고 했을까? 왜 계속 정답을 물어보려 했을까?”

기술은 계속 발전됨에 따라 어제 제일 좋았던 답이, 오늘은 안좋은 답이 될 수도 있는 것 같다.

내가 si에서 일했을 때, 같이 일하던 사람들에게 많이 말하고 생각했던 말들이 떠올랐다.
“그 코드는 어제의 최선이었다.”
“어... 이거 왜 이렇게 짰었지? 고쳐봐야겠다.”

한낱 완전 초짜 신입도 내가 짠 코드를 몇 일 뒤에 보면 이상해 보이고 그러는데, 그 똑똑하고 실력 뛰어난 개발자들이 개발하는 기술은 하루가 다르게 달라지는게 당연한 것 같다.

앞으로 정답을 쫓기 보다는, 이 시점에 적절한 건 무엇일까? 왜 이게 적절할까?를 고민하며 Trade-off에 관한 고민도 많이 해봐야겠다는 생각을 했다.


배운점

[기술]

  1. setter를 이용한 초기화는 신뢰할 수 없는 객체가 된다.
    • 불변 객체가 성립되도록 빌더패턴등을 활용하자
  2. 객체의 생성자가 전체 코드에서 단 하나라면, @Autowired 생략이 가능하다.
    • Spring 4.3부터 단일 생성자가 있는 클래스 또한 @Autowired 주석 생략 가능
  3. 동시성 해결법은 SynchronizedAtomic이 있다
    • [Synchronized]
      • synchronized 메서드는 클래스의 인스턴스 단위로 동기화가 일어난다 (lock)
      • static synchronized 메서드는 해당 클래스의 객체 단위로 동기화가 일어난다 (lock)
      • id도 같이 static으로 설정함으로써 static 메서드와 동기화가 이루어진다

        static synchronized 동작 방식은 클래스 단위로 lock이 발생하며, 하나의 쓰레드에서 메서드에 접근 중이면 다른 쓰레드가 접근하지 못하도록 lock을 걸고 대키 큐로 보낸 후, 메서드가 종료 될 시 lock을 해제하고 대기 큐의 작업 1개를 실행하는 방식

    • [Atomic]
      • AtomicLong 내부의 value는 vilatile로 선언되어있고, 이는 코어가 변수의 값을 읽어올 때 캐시가 아닌 메인 메모리에서 읽어옴으로써 멀티 쓰레드 환경에서 값의 불일치가 해결되는 원리

[교양]

  1. 타인이 코드를 읽을 때 조금이라도 생각해야되는 문법이라면 사용을 지양하자

  2. 블로그로 쉽게 얻기보다는 어렵더라도 공식 문서를 추구하기

    (이해가 안되는건 적은 경험, 지식 때문일수도..)

  3. 시간이 흐르면 답은 항상 달라짐

    • 기술이 발전할수록 항상 답은 달라지기 때문에 항상 정답이 있다고 생각하지 않기
    • 정답은 없으므로 다양한 관점, 시각에서 기술을 바라보는 것이 좋을 것 같음
      • ex) Lombok이 마냥 편하고 진리인 라이브러리로 환영 받던 예전과 달리 지금은 롬복이 별로다, 위험하다라는 개발자들이 많아지고, 더 나아가서는 코틀린에서는 필요가 없다라고도 한다.
  4. 주석을 다는 것은 언제나 옳다.

    • 네이밍 규칙과 주석을 붙이는 것은 다르다
    • 주석이 코드의 가독성을 해친다고 생각하지 말자
    • 코드의 의도를 들어내기, 구체적보다는 추상적으로 작성하기, 코드에서 설명할 수 없는 것들은 꼭 작성하기
    • 인터페이스에는 주석이 필수다
  5. 코드를 짤 땐 항상 어떻게 하는지보다 무엇을 하는지 보이게 작성하자

profile
꾸준함의 가치를 향해 📈

0개의 댓글