2023년 2월 19일 일요일

Spring Data JDBC 알아보기

아주 오랜만에 공부 글 작성...

스터디하면서 검색한 내용을 메모 

  •  스터디 issue : Spring Data 활용한 CRUD 구성

 

------------------------------------------------------

Spring Data

  • 데이터 저장소라는 특징은 유지하면서, 데이터 접근을 위한 친숙하고 일관된 Spring 기반의 프로그래밍 모델

    • 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자의 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌!
  • 데이터 엑세스 기술, 관계형/비관계형 데이터베이스, map-reduce 프레임워크(대용량 데이터 처리를 분산 병렬 컴퓨팅에서 처리하기 위한 목적), 클라우드 기반의 데이터 서비스 등을 쉽게 사용할 수 있게 해줌

    • 기존 관계형 DB 뿐만 아니라 폭 넓은 지원
  • 데이터베이스에 대한 하위 프로젝트들을 포함하는 상위프로젝트 개념

  • 특징

    • repository 메서드 이름에서 동적쿼리 파생
    • 기본 속성을 제공하는 기본 도메인 구현
    • auditing 지원 (created, last changed)
    • javaConfig or xml 을 통합 쉬운 Spring 설정
    • 향상된 Spring MVC 컨트롤러와의 통합
  • 주요 모듈

    • Spring Data Commons

      • 모든 Spring Data 모듈을 뒷받침하는 핵심 개념
      • Spring Data JPA 쓸 때 자주 사용하는 JpaRepsitory 를 타고 올라가다보면 결국 Spring Data Commons 을 볼 수 있음
      • repository
    • Spring Data JDBC

      • JDBC에 대한 Spring Data repository 지원
    • Spring Data JPA

      • JPA 에 대한 Spring Data repository 지원
    • Spring Data KeyValue, Spring Data MongoDB, Spring Data Redis, Spring Data REST 등

Spring Data 장단점

  • 장점

    • 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자의 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌
    • repository 메서드 이름에서 동적쿼리 파생
  • 단점

    • 어렵다 (각 하위 프로젝트들 사용에 있어서 학습시간이 필요함. 학습범위가 넓음)

Spring Data / JPA 구분 및 정리

구성

  • Spring Data

    • 위 내용과 같이 상위 프로젝트 개념
  • ORM

    • 객체 관계 매핑 (Object Relational Mapping)
    • 객체는 객체대로 설계
    • 관계형 데이터베이스는 관계형 데이터베이스대로 설계
    • 객체와 관계형 데이터베이스 를 ORM 프레임워크가 중간에서 매핑해준다
  • JPA

    • 자바 ORM 기술 표준. 인터페이스 모음
    • 구현체 : Hibernate, EclipseLink 등
    • (entityManager로 직접 persist, find 등 실행)
    • SQL 중심 개발 -> 객체 중심으로 개발하기 위함
    • 역사
      자바 과거 진영은 EJB 기슬 표준을 만들었는데 그 안에는 엔티티 빈이라는 ORM 기술도 포함되어 있었음
      하지만 너무 복잡하고 기술 성숙도도 떨어졌으며 자바 엔터프라이즈 애플리케이션 서버에서만 동작했음
      이때 하이버네이트라는 오픈소스 ORM 프레임워크가 등장했는데 EJB의 ORM 기술과 비교해서 가볍고 실용적인데 다 기술 성숙도도 높았음
      또한 자바 엔터프라이즈 애플리케이션 서버 없이도 동작해서 많이 사용하기 시작함
      결국 EJB 3.0 에서 하이버네이트를 기반으로 새로운 자바 ORM 기술 표준이 만들어짐 : JPA
      
    • JPA 사용 이유
      • 생산성
        • 자바 컬렉션에 객체를 저장하듯이 JPA에게 저장할 객체를 전달하면 됨
        • INSERT SQL을 작성하고 JDBC API를 사용하는 지루하고 반복적인 일은 JPA가 대신 처리해 줌
        • 지루하고 반복적인 CRUD용 SQL을 개발자가 직접 작성하지 않아도 됨
        • 이런 기능들을 사용하면 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬 수 있음
      • 유지보수
        • SQL에 의존적인 개발은 SQL을 직접 다루기 때문에 엔티티에 필드 하나만 추가해도 관련된 등록, 수정, 조회 SQL 과 결과를 매핑하기 위한 JDBC API 코드 모두 변경해야한다.
        • 반면 JPA를 사용하면 이런 과정을 JPA가 대신 처리해주므로 수정해야할 코드가 줄어든다.
        • 또한 JPA가 패러다임의 불일치 문제를 해결해주므로 객체지향 언어가 가진 장점들을 활용해서 유연하고 유지보수하기 좋은 도메인 모델을 편리하게 설계할 수 있음
      • 패러다임의 불일치 해결
        • JPA는 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치 문제를 해결해줌
      • 성능
        • JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공함
        • 애플리케이션과 데이터베이스 사이에 계층이 하나 더 있으므로 최적화 관점에서 시도해볼 수 있는 것들이 많음
      • 데이터 접근 추상화와 벤더 독립성
        • 관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많음
        • JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 함
      • 표준
        • 자바 진영의 ORM 기술 표준임
        • 표준을 사용하면 다른 구현 기술로 손쉽게 변경할 수 있음
  • Hibernate

    • ORM 프레임워크
    • JPA의 구현체
    • 내부적으로 JDBC API를 사용

Spring Data JDBC vs Spring Data JPA 비교

  • Spring Data JDBC

    • Spring Data JDBC는 JDBC 기반 repositories 구현을 쉽게 해줌

    • JPA는 다양하고 편리한 기능을 제공하는데, 해당 기능이 단점이 될 수도 있음. 특정 작업이 수행되는 이유를 파악하기 어려울 수 있다. 단순한 개념도 JPA에선 어려울 수도 있다.

      • ex) 예상하지 못한 Entity 정보까지 가져올 수 있음
      • ex) lazy loading 발생이 언제하는 지 바로 파악하기 어려울 수 있음. 연관이 깊게 되어있는 경우 JPA가 제공하는 기능이 오히려 어려워질 수 있음
    • 그래서 Spring Data JDBC는 아래의 설계로 simple한 개념에 목표로 함

      • entity가 로드되는 시점에 SQL문이 실행되고 entity 로딩이 완료됨, 엔티티를 save 해야 저장된다
      • vs JPA
        • No EntityManager (PersistenceContext)
        • No Caching (1차 캐시)
        • No Lazy Loading
          • Aggregate를 정리하고 복합조회용 객체를 분리하면 Lazy loading이 필수일지 한번 더 생각해볼 수 있다.
          • 반대로 Lazy loading이 있어서 깊은 객체 그래프의 Aggregate를 설계하고자하는 유혹에 빠질 수도 있다.
          • Lazy loading이 복잡한 엔티티, 슈퍼 엔티티가 나올 수 있는 함정이 있음
          • Lazy loading이 필요하다는 것은 모델링을 다시 생각해봐야한다는 신호일 수도 있다
        • No Dirty Tracking
        • No Proxy
          • JPA에서는 dirty tracking, lazy loading, cache 이런 것들을 하기 위해 proxy 를 만들고 있음
        • 위의 것들은 Spring Data JDBC의 방향성과 맞지 않는다
    • Domain Driven Design

      • 모든 Spring Data 모듈은 Domain Driven Design 의 'repository', 'aggregate', 'aggregate root' 개념에서 영감을 얻었음
      • aggregate 는 원자적인 변경간에 일관성을 보장하는 엔티티의 그룹
        • Transaction, Lock의 필수 범위
        • 불변식(Invariants, 데이터가 변경될 때마다 유지돼야 하는 규칙)이 적용되는 단위
        • ex) OrderItems가 있는 Order
          • Order 속성은 변경 내용에 따라 일관성을 유지함
          • ex) Order의 numberOfItems 속성은 OrderItems 의 수와 일치함
      • 각 aggregate 에는 aggregate 의 엔티티 중 하나인 aggregate root 가 1개씩 있음
        • aggregate는 오직 aggregate root의 메서드를 통해서만 조작됨 -> 이것들이 앞서 언급된 원자적인 변경들임
        • root를 통해서만 조작이 되므로 일관성을 유지시킬 수 있음
      • 일반적으로 Spring Data의 경우 aggegate root당 1개의 Repository 를 가짐 (AGGREGATE 1개당 REPOSITORY 1개)
        • Spring Data JDBC 의 경우 aggregate root에서 접근할 수 있는 모든 엔티티가 해당 aggregate root의 일부로 간주됨을 의미함
        • aggregate root 를 통해서 aggregate 밖에서 aggregate 안의 객체로 접근함
      • aggregate 경계가 있는 시스템의 경우
        • 분리할 계획이 없더라도 코드를 고칠 때 영향성을 파악하기가 유리하다
        • 별도의 저장소나 API 서버를 분리할 때 상대적으로 유리
        • aggregate 별로 Cache 를 적용하기에도 좋다
      • aggregate 간의 참조
        • 다른 aggregate 의 root를 직접 참조하지 않고 ID로만 참조하기
        • Spring Data JDBC 에는 같은 역할을 하는 AggregateReference 라는 기능을 제공하고 있음 (id로 참조)
    • 프레임워크는 설계를 거드는 역할이 되어야 한다

      • 편의성이 주는 기능이 설계를 해치는 지 경계해야 한다
      • 때로는 다른 가치를 위해 더 긴 코드를 만들 수도 있다
        • 고치기 쉬운, 협업하기 쉬운, 확장하기 쉬운 코드를 위해서!
    • 구현체 : SimpleJdbcRepository

    • 구현 예시

  • Spring Data JPA

    • 스프링 프레임워크와 JPA 기반 위에 JPA를 편리하게 사용하도록 도와주는 기술
    • JPA를 추상화시킨 Repository 라는 인터페이스를 제공함
      • JpaRepository 구현체를 쭉 타고 들어가보면 SimpleJpaRepository 로 가는데 잠깐 소스 보면 entityManager를 쓰고 있음 (em.find(~~~))
      • (vs JDBC : SimpleJdbcRepository)
    • entity의 변경사항 추적 (lazy loading)
      • lazy loading: 필요 시점까지 리소스 로딩을 연기하다가 필요할 때 로딩하는 것
    • 구현체 : SimpleJdbcRepository

참고자료


 ------------------------------------------------------

DDD

  • DDD ?

    • Domain-Driven Design
    • 해당 도메인과 일치하도록 소프트웨어를 모델링하는 데 중점을 둔 소프트웨어 설계 접근 방식이다
    • 데이터 중심의 접근이 아니라 Domain 에 집중함
  • DDD 개념의 구성 요소 (DDD START 책)

    • ENTITY (엔티티)

      • 고유 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
        • 주문, 회원, 상품 과 같이 도메인의 고유한 개념을 표현함
      • 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.
      • 관계형 모델의 엔티티와 같은 것이 아님!
        • 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공함
          public class Order {
            // 주문 도메인 모델의 데이터 
            private OrderNo number;
            private Orderer orderer;
            ...
          
            // 도메인 기능 제공
            public void changeShippingInfo(ShippingInfo shippingInfo) {
              ...
            }
          }
        • 단순히 데이터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체
          • 도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경하는 것을 막는다
    • VALUE (밸류)

      • 고유의 식별자를 갖지 않는 개체로 주로 개념적으로 하나인 값을 표현할 때 사용된다.
        • 배송지 주소를 표현하기 위한 주소나 구매 금액을 위한 금액과 같은 타입이 밸류 타입
      • 엔티티의 속성으로 사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용할 수 있다.
    • AGGREGATE (애그리거트)

      • aggregate
      • 도메인이 커질 수록 많은 엔티티와 밸류가 출현하는데, 상위 수준에서 모델을 관리하지 않고 개별 요소에만 초점을 맞추다보면, 큰 수준에서 모델을 이해하지 못해 큰 틀에서 모델을 관리할 수 없는 상황에 빠질 수 있음
      • 도메인 모델에서 전체 구조를 이해하는 데 도움이 되는 것이 aggregate
      • aggregate는 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
        • 주문과 관련된 '주문', '배송지 정보', '주문자', '주문 목록', '총 결제 금액'의 하위 모델을 '주문' 이라는 상위 모델로 묶을 수 있다
      • aggregate를 사용하면 개별 객체가 아닌 관련 객체를 묶어서 객체 군집 단위로 모델을 바라 볼 수 있게 됨
      • aggregate root
        • aggregate는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다
        • 루트 엔티티는 aggregate에 속해 있는 엔티티와 밸류 객체를 이용해서 aggregate가 구현해야할 기능을 제공함
        • aggregate 를 사용하는 코드는 aggregate root가 제공하는 기능을 실행하고 aggregate root를 통해서 간접적으로 aggregate 내의 엔티티나 밸류 객체에 접근한다
          • ex) 주문 aggregate는 Order 를 통하지 않고는 ShippingInfo 를 변경할 수 있는 방법을 제공하지 않는다
        • aggregate root가 제공하는 메서드는 도메인 규칙에 따라 aggregate에 속한 객체의 일관성이 깨지지 않도록 구현해야 한다.
    • REPOSITORY (리포지터리)

      • 도메인 모델의 영속성을 처리한다
        • DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능을 제공한다.
      • repository는 aggregate 단위로 도메인 객체를 저장하고 조회하는 기능을 정의함
        • Order 는 aggregate 에 속한 모든 객체를 포함하고 있으므로 결과적으로 aggregate 단위로 저장하고 조회함
      • aggregate는 개념상 완전한 한 개의 도메인 모델을 표현하므로 객체의 영속성을 처리하는 repository는 aggregate 단위로 존재한다.
        • ex) Order 와 OrderLine 을 물리적으로 각각 별도의 DB 테이블에 저장한다고해서 Order와 OrderLine을 위한 repository를 각각 만들지 않는다.
        • Order 가 aggregate root고 OrderLine은 aggregate에 속하는 구성요소이므로 Order를 위한 repository만 존재한다
    • DOMAIN SERVICE (도메인 서비스)

      • 특정 엔티티에 속하지 않은 도메인 로직을 제공한다
        • '할인 금액 계산'은 상품, 쿠폰, 회원 등급, 구매 등급 등 다양한 조건을 이용해서 구현하게 되는데, 이렇게 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현한다.
  • 책에 나온 JPA 관련 내용

    • JPA는 @manytoone@OnetoOne 과 같은 애너테이션을 이용해 연관된 객체를 로딩하는 기능을 제공하고 있으므로 필드를 이용해 다른 aggregate를 쉽게 참조할 수 있다.
    • ORM 기술 덕에 aggregate root 에 대한 참조를 쉽게 구현할 수 있고 필드를 이용한 aggregate 참조를 사용하면 다른 aggregate 데이터를 쉽게 조회할 수 있다.
    • 하지만 필드를 이용한 aggregate 참조는 다음 문제를 야기할 수 있다
      • 편한 탐색 오용
        • 한 애그리거트 내부에서 다른 애그리거트 객체에 접근할 수 있으면 다른 애그리거트의 상태를 쉽게 변경할 수 있게 된다
        • 한 애그리거트에서 다른 애그리거트의 상태를 변경하는 것은 애그리거트 간의 의존 결합도를 높여서 결과적으로 애그리거트의 변경을 어렵게 만든다
      • 성능에 대한 고민
        • 지연로딩, 즉시로딩 고민
      • 확장 어려움
        • 부하를 분산하기 위해 하위 도메인별로 시스템을 분리할 때 다른 애그리거트 참조하기 위한 기술을 사용할 수 없음
    • 이런 세 가지 문제를 완화할 때 사용할 수 있는 것이 ID를 이용해서 다른 aggregate 를 참조하는 것이다.
      • ID 참조를 사용하면 모든 객체가 참조로 연결되지 않고 한 애그리거트에 속한 객체만 참조로 연결된다.
      • 이는 aggregate 의 경계를 명확히하고 aggregate 간 물리적인 연결을 제거하기 때문에 모델의 복잡도를 낮춰준다.
      • 참조하는 aggregate가 필요하면 응용 서비스에서 ID를 이용해서 로딩하면 된다.
      • ID를 이용한 참조 방식을 사용하면 복잡도를 낮추는 것과 함께 한 aggregate에서 다른 aggregate를 수정하는 문제를 근원적으로 방지할 수 있다.
      • aggregate 별로 다른 구현 기술을 사용하는 것도 가능해진다.
        • ex) 주문 aggregate는 RDBMS, 상품 aggregate 는 NoSQL 등

 ------------------------------------------------------

  1. 왜 Spring Data JDBC 는 Lazy Loading, Dirty Tracking 을 안할까?

    • Spring Data JDBC 는 ddd 철학을 가지고 나왔기 때문임
    • ddd는 Aggregate root 을 통해서만 변경을 추구함
    • JPA가 제공하는 편리한 기능은 예상하지 못한 곳에서 변경이 발생함. 일관성이 깨질 수도 있다?
    • Spring Data JDBC는 객체와 관계형 데이터베이스 간의 매핑을 최소화하기 위한 기술임. 따라서, 객체지향 프로그래밍에서 일반적으로 사용되는 Lazy Loading, Dirty Tracking과 같은 기능을 제공하지 않는다.
    • 그럼 Spring Data JPA 에는 왜 있을까?
      • 해당 기능은 영속성 컨텍스트(Persistence Context) 에 의해 가능함
      • 생긴 배경은, 객체-관계 매핑(ORM)이라는 기술과 연관 있는데 객체지향 프로그래밍 언어와 관계형 데이터베이스의 데이터 모델 사이의 불일치를 해결하기 위함
  2. Aggregate 랑 Aggregate root 차이점

    • Aggregate는 객체 모델링에서 도메인 객체들을 논리적으로 묶어서 관리하는 단위이며, Aggregate root는 Aggregate를 구성하는 루트 개체
    • Aggregate
      • 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것
      • ex) 주문, 배송지 정보, 주문자, 주문목록, 총 결제 금액 등의 모델들을 '주문'이라는 상위 모델(aggregate)로 묶을 수 있다.
    • Aggregate root
      • 군집에 속한 객체를 관리하는 root
      • Aggregate root는 다른 객체에서 참조할 수 있는 진입점(entry point) 역할을 하며, Aggregate root를 통해서만 Aggregate 내부의 다른 객체에 접근할 수 있습니다. 이렇게 Aggregate root가 중심이 되어 Aggregate 내부의 객체들은 일종의 클로저(closure)처럼 동작함
      • Aggregate root는 또한 Aggregate 내부의 일관성(invariant)을 보장하는 책임도 가지고 있음. 즉, Aggregate root를 통해서만 Aggregate 내부의 객체들의 상태를 변경할 수 있으며, Aggregate root의 변경으로 인해 Aggregate 내부의 객체들의 상태가 불일치하는 경우를 방지함
      • root entity 는 Aggregate에 속해 있는 엔티티와 벨류 객체를 이용해서 Aggregate가 구현해야할 기능을 제공한다.
      • ex) 주문 Aggregate 는 Order(root) 을 통해서만 ShippingInfo를 변경할 수 있다. 이를 통해 ShippingInfo가 일관성 없게 바뀌는 것을 막아줌
  3. Spring Data JDBC는 왜 ManyToOne, ManyToMany 를 미지원할까?

    • Spring Data JDBC 는 Entity 설계 시 Aggregate 개념 적용을 강하게 주장한다.
    • Aggregate 개념에 위배된다 (필요하지 않은 개념이다)
      • Aggregate는 오직 Aggregate root의 메서드를 통해서만 조작된다. 다른 Aggregate 의 데이터 변경이 필요하다면 Aggregate root 를 통해서 접근되어서 수정이 발생해야 한다.
      • = 하나의 Aggregate는 하나의 Repository 를 통해서 영속성을 관리한다.
    • 위 Aggregate 개념에서 다른 Aggregate 객체 접근할 수 있으면 다른 Aggregate 의 상태를 쉽게 변경할 수 있다
      • 한 Aggregate에서 다른 Aggregate 상태를 변경하는 것은 Aggregate 간의 의존 결합도를 높여서 결과적으로는 Aggregate 의 변경을 어렵게 만든다

2022년 2월 1일 화요일

자바 성능 튜닝 이야기 책 메모

오랜만에 책 메모!!

꼭 자바 성능에 대한 내용이다 라기 보다는 시스템 전반적인 성능에 대해 참고해야할 내용을 설명하는 느낌이었다.
엄청 생소한 내용보다는 어디선가 나오는 '이런건 하지마라~' 를 모아놓은 느낌?!

책의 내용을 바탕으로 문제 발생 시 검색 능력이나 원인 파악을 향상 시킬 수 있을 것 같다.


시스템의 성능이 느릴 때 가장 먼저 해야하는 작업은 병목 지점을 파악하는 것이다.

...

성능 튜닝의 비법

- 하나만 보지 말아라
- 큰 놈을 없애라
- 깊게 알아야 한다
- 결과 공유는 선택이 아닌 필수


해당 챕터는 다시 볼만하다. 

  • 01_디자인 패턴 꼭 써야 한다

  • 03_왜 자꾸 String을 쓰지 말라는 거야

  • 04_어디에 담아야 하는지...

  • 06_static 제대로 한번 써 보자

  • 08_synchronized는 제대로 알고 써야 한다

  • 16_JVM은 도대체 어떻게 구동될까?

  • 17_도대체 GC는 언제 발생할까?

2022년 1월 17일 월요일

2018~2021 간단 회고

 

2018~2021 간단 회고

  • 잘한 점

    • 백기선님&김영한님 강의, 이동욱님 블로그를 알게 된 것
    • 인터넷 강의 구매에 돈을 아끼지 않은 점
    • 토이프로젝트를 통해 관심 있는 것들은 직접 경험해본 것
    • 새로운 업무환경으로 도전한 것
  • 아쉬웠던 점

    • 분명 공부했던 내용인데 까먹은 것이 많았음
    • 주로 혼자 공부한 점
    • 블로그 활동이 적음 (새로운 것이 없다면 예전에 잘못 작성한 글에 대한 수정하는 것도 좋아 보임)
    • 회사업무 중 효율적인 프로세스 도입에 대한 고민과 적극성이 부족했음
  • TODO

    • 영양가있는 블로그 글 쓰기
    • 스터디를 통한 개발 자극 얻기
    • 부족하다고 생각되는 기본 개념은 꼭 복습하기

2018년

  • 신입사원 회사 적응..

2019년

  • 2019년 읽은 책 : 7권

  • 완료한 강의 : 3개

    • 스프링 웹 MVC_백기선
    • 스프링 프레임워크 핵심기술_백기선
    • JPA 프로그래밍 기본다지기_김영한
  • git : 48 contributions in 2019

  • 블로그 : 12개 업로드

  • 2019년 학습 만족도

    • 베이스: 6점. 미루고 미뤘던 자바의 정석 복습 끝냄. 이펙티브 자바 읽기(가볍게 읽음). 자바8 겉핥기.
    • 프레임 워크: 6점. 백기선님 강의를 통해 스프링 프레임워크에 대해 많이 배움(정리는 해놨지만, 직접 써봐야 내것이 될듯). NoN_Ver2(토이프로젝트) 진행 중
    • 알고리즘: 2점. 기본서는 읽었지만 읽기만 해서 남는게 없음. 코딩테스트는 커녕 기본 자료구조 학습도 거의 안함(4챕터 진행중)
    • 데이터베이스: 2점. 기본SQL 아직 부족함. JPA 기본강의 들음(너무 신기하다. 잘 써보고싶다)
    • 회사 업무: 6점. 쿼리 튜닝, 기본 쿼리 작성에 부족합을 느꼈지만 그 외는 꼼꼼하게 잘 마무리했다고 생각함. 재미있는 개발은 많이 없어서 아쉬움. (잡일이 좀 많았음)

참 많은 일이 있었던 해였음.
병원에서 자는 일이 너무 많았음. (물론 좋은 일도 있었음)
공부한다고 항상 무언가를 하긴 하였으나, 개발자로 성장을 하였거나 코딩을 엄청하거나 그런 느낌이 없다.
강의들은 것은 좋으나 직접 활용해보지 않아 의미없다. (새롭게 알게된게 엄청 많긴한데.... 토이프로젝트에서 적용을 해봐야 함 아니면 까먹을듯...)
메모 습관 중요성 느낌!!(2020년에는 트렐로를 활용해보기로)
토이프로젝트 진행이 너무 더뎠음.
회사에서는 스트레스를 너무 많이 받았음. (업무과부하, 답답함) 맥북 램 16기가로 살껄.... 후회된다...

2020년

  • 2020년 읽은 책 : 9권

  • 완료한 강의 : 4개

    • 스프링 기반 REST API 개발_백기선
    • 더 자바-코드를 조작하는 다양한 방법_백기선
    • 자바 ORM 표준 JPA 프로그래밍-기본편_김영한
    • 실전! 스프링 데이터 JPA_김영한
  • git : 280 contributions in 2020

  • 블로그 : 16개 업로드

  • 2020년 학습 만족도

    • 베이스: 7점. 모던자바, 이펙티브 자바 책 끝냄. 아직 익숙하진 않지만 토이프로젝트에서 사용하려고 노력 중임
    • 프레임 워크: 7점. NoN_Ver2 완성, 스프링 강의, JPA 강의들이 아주 유익했음
    • 알고리즘: 2점. 20년 초반에 자료구조 끝내고 기본 문제를 풀다가 멈춤.
    • 데이터베이스: 1점. 기본SQL 공부 안했음
    • 회사 업무: 5점. 고민할 일이 너무 없다. 주어진 것에 대해서는 깔끔하게 처리하였으나 잘 했다고는 볼 수 없어보인다.

토이 프로젝트를 많은 것을 해볼 수 있었다.
인강에서 학습한 REST API, JPA 를 실습해볼 수 있었고, 멀티모듈 실습 등 재미있는거 많이 해볼 수 있었음!
github 에 커밋이력 채운다고 책 메모도 커밋하고 쌩쇼를하였지만, 결국 코딩을 해야한다.
21년엔 책 메모는 되도록 줄여서 하자.
최근 토이프로젝트를 통해 람다, 스트림을 활용하려고 하고 있다. 당연히 잘 써야한다.
들었던 강의 만족도는 아주 높다. 특히 김영한님 강의는 아주 좋다.
올해는 산거 다 들어보자. 학습 시간을 줄일 수 있다. 이번주 계획 툴로는 트렐로 만족한다. 계속 쓰자.

2021년

  • 2021년 읽은 책 : 4권

  • 완료한 강의 : 3개

    • 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화_김영한
    • 실전! Querydsl_김영한
    • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술_김영한
  • git : 349 contributions in 2021

  • 블로그 : 8개 업로드

  • 2021년 학습 만족도

    • 베이스: 6점. 자바 성능 튜닝이야기 책 읽음. 이펙티브 자바 내용을 다 까먹은 듯하다. 다시 복습이 필요할 때임
    • 프레임 워크: 7점 : todolist(토이프로젝트) 완성, 스프링 배치가 무엇인지 정도 공부함..
    • 알고리즘: 7점 : 기본문제, 백준, 프로그래머스 단계별로 풀었음(계획대로 하였고, 제일 진도를 많이 나간 해였음)
    • 데이터베이스: 6점 : 기본SQL 책 사서 복습함
    • 회사 업무: 7점 : 나름 뭔가 많이 경험함. 서버설정, 배치프로그램 생성 등 나쁘진않았음

이번 년도 역시 토이프로젝트로 많은 것을 해볼 수 있었다.
연습이나 학습했던 것들을 직접 프로젝트에 적용해볼 수 있었다. 배포 자동화를 해보면서 재미있었고, 블로그에 쓸 내용도 있어서 좋았고 테스트코드를 짜려고 노력하였음!
또한 출근 전 1시간 공부가 많은 것을 할 수 있게 해줬음!(올해는 많이 안빼먹고 잘했음!!)

올해는 이직준비를 하여서 개념공부, 알고리즘 공부 등에 집중하였는데 개념공부는 '애매하게 알면 안된다' 알고리즘은 '꾸준하게 하자' 를 느꼈다.
역시 김영한님의 인터넷 강의 만족도는 높았다.

22년엔 재밌는 공부 많이하고, 좋은 책들 많이 읽자! (일단 오브젝트 책부터 ...!)

2021년 8월 17일 화요일

토이프로젝트 API 개선하기 (REST 하게..)

 

  • 토이프로젝트에 생성한 api는 REST 하지 못한 것 같다. (단순 json 응답임)

    • REST하게 개선해보자 (백기선님의 REST API 강의 참고)

      // 기존 소스
      @RequiredArgsConstructor
      @RestController
      @Slf4j
      @RequestMapping(value = "/api/board")
      public class BoardApiController {
          private final BoardService boardService;
      
      
              @PostMapping
              public ResponseEntity addBoard(@RequestBody BoardRequestDto boardRequestDto, @LoginUser SessionUser user) {
                  Map<String, Object> resultMap = new HashMap<>();
      
      
                  Long result = boardService.save(boardRequestDto, user.getEmail());
      
      
                  resultMap.put("result", result);
                  resultMap.put("resultMessage", "success");
      
      
                  return new ResponseEntity<>(resultMap, HttpStatus.OK);
              }
              ...
      }
      

REST API

  • REpresentational State Transfer(REST) + Application Programming Interface(API)

  • 서로 다른 시스템간의 독립적인 진화를 위함

  • REST 아키텍처 스타일을 따르는 API

    • Client-Server, Cache, Stateless, Layered System, ...

    • Uniform Interface : URL로 지정된 리소스에 대한 조작을 통일하고 한정된 인터페이스로 수행하는 아키텍쳐 스타일

      • Indentification of resources, ...

      • Hypermedia as the Engine of Application State(HATEOAS)

        • 하이퍼미디어(링크)를 통해 애플리케이션 상태 변화가 가능해야한다.

        • 링크 정보를 동적으로 바꿀 수 있다.(Versioning 할 필요 없이!)

        • 응답을 받은 다음에 다음 애플리케이션 상태로 전이를 하려면 서버가 보내준 응답에 들어있는 링크정보를 사용해서 이동을 해야한다.

        • HATEOAS 로 만들려면?

          • HAL을 통해 링크를 정의 (다른 방법도 있음)
      • self-descriptive message

        • 메시지 스스로 메시지에 대한 설명이 가능해야함

        • 서버가 변해도 클라이언트는 그 메시지를 보고 해석이 가능하다

        • 확장 가능한 커뮤니케이션

        • self-descriptive message로 만들려면?

          • HAL의 링크 데이터에 profile 링크 추가 (다른 방법도 있음)

HAL ?

  • Hypertext Application Language

  • API의 리소스 간에 일관되고 쉬운 하이퍼링크 방법을 제공하는 간단한 형식

  • REST API에 HAL을 포함하면 기본적으로 자체 문서화될 뿐만 아니라 사용자가 훨씬 더 쉽게 탐색할 수 있음

Spring HATEOAS ?

  • 스프링 프로젝트 중 하나로 주요 목적은 API를 만들 때 리소스를 REST 하게 쉽게 제공해주기 위한 툴을 편리하게 사용할 수 있게 해주는 라이브러리

  • 하이퍼미디어를 사용해서 클라이언트가 애플리케이션 서바와 동적으로 정보를 주고 받을 수 있는 방법

  • 여러 기능을 제공하는데 가장 중요한 기능은 링크를 만드는 기능, 리소스(응답+링크)를 만드는 기능임

    • 링크
      • HREF
      • REL (현재 릴레이션과의 관계)
        • self
        • profile
        • ... (조회, 업데이트 등 링크)

Spring Rest Docs ?

  • Spring MVC Test를 사용해서 REST API 문서의 조각들을 생성하는데 유용한 툴

    • 테스트에 사용된 정보가 문서로 생성됨
  • 이 문서 조각을 모아서 REST API 가이드 문서를 생성할 수 있음 (html)


실습

Spring HATEOAS

  • 백기선님의 REST API 강의에서는 Resource 사용하였지만 Spring HATEOAS 버전 변경에 따라 이름이 변경됨

  • Resource를 사용하여 구성한 body를 EntityModel 으로 바꿔보자

    • 강의 예제 소스

      public class EventResource extends Resource<Event> {
          public EventResource(Event event, Link... links){
              super(event, links);
              add(linkTo(EventController.class).slash(event.getId()).withSelfRel());
          }
      }
      
      
      ...
      
      
      @PostMapping
      public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto,
                                        Errors errors, @CurrentUser Account currentUser){
      
      
          // @Valid를 통해 검증한 에러들을 errors에 담고 검증하여 badRequest
          if(errors.hasErrors()){
              return badRequest(errors);
          }
          // validate를 통해 검증한 에러들을 errors에 담고 검증하여 badRequest
          eventValidator.validate(eventDto, errors);
          if(errors.hasErrors()){
              return badRequest(errors);
          }
      
      
          Event event = modelMapper.map(eventDto, Event.class);
          event.update();
          event.setManager(currentUser);
      
      
          Event newEvent = this.eventRepository.save(event);
          ControllerLinkBuilder selfLinkBuilder = linkTo(EventController.class).slash(newEvent.getId());
          URI createUri = selfLinkBuilder.toUri();
          EventResource eventResource  = new EventResource(event);
          eventResoruce.add(linkTo(EventController.class).withRel("query-events"));
          eventResoruce.add(selfLinkBuilder.withRel("update-event"));
          eventResoruce.add(new Link("/docs/index.html#resources-events-create").withRel("profile"));
      
      
          return ResponseEntity.created(createUri).body(eventResource);
      }
      
    • 예제를 참고하여 개선한 토이프로젝트 소스

      @PostMapping
      public ResponseEntity addBoard(@RequestBody BoardRequestDto boardRequestDto, @LoginUser SessionUser user, Errors errors) {
      
      
          boardValidator.validate(boardRequestDto, errors);
          if(errors.hasErrors()){
              log.debug("잘못된 요청입니다. error: " + errors.getFieldError());
              return ResponseEntity.badRequest().body(CollectionModel.of(errors.getAllErrors()));
          }
      
      
          Long result = boardService.save(boardRequestDto, user.getEmail());
          boardRequestDto.setId(result);
      
      
          WebMvcLinkBuilder webMvcLinkBuilder = linkTo(BoardApiController.class).slash(result);
      
      
          // 링크 제공
          EntityModel<BoardRequestDto> entityModel = EntityModel.of(boardRequestDto);
          // profile
          entityModel.add(linkTo(IndexController.class).slash("/docs/index.html#resources-add-board").withRel("profile"));
          // self
          entityModel.add(webMvcLinkBuilder.withSelfRel());
          // update
          entityModel.add(webMvcLinkBuilder.withRel("update-board"));
      
      
          return ResponseEntity.created(webMvcLinkBuilder.toUri()).body(entityModel);
      }
      
    • response body

      • _links 를 통해 다양한 상태의 링크를 서버가 제공한다. (HATEOAS)
      {
          "id":30,
          "boardName":"boardName1",
          "delFlag":"N",
          "_links":{
              "profile":{
                  "href":"http://localhost:8080/docs/index.html#resources-add-board"
              },
              "self":{
                  "href":"http://localhost:8080/api/board/30"
              },
              "update-board":{
                  "href":"http://localhost:8080/api/board/30"
              }
          }
      }
      

Spring Rest Docs

  • 문서조각 만들기

    • build.gradle 에 다음을 추가하고 test 코드에 document 관련 코드를 추가하면 문서 조각이 생성된다.

      dependencies {
              ...
              testImplementation('org.springframework.restdocs:spring-restdocs-mockmvc')
              ...
      }
      
      @SpringBootTest
      @Transactional
      @AutoConfigureMockMvc
      @AutoConfigureRestDocs
      @ActiveProfiles("test")
      class BoardApiControllerTest {
      
      
      @Test
      @DisplayName("보드 등록 api 테스트")
      public void addBoardApiTest() throws Exception{
          //given
          BoardRequestDto boardRequestDto = new BoardRequestDto("boardName1");
      
      
          User user = new User("testUser1", "email", "picture", Role.ADMIN);
          userRepository.save(user);
      
      
          // 세션 정보
          SessionUser sessionUser = new SessionUser(user);
          mockHttpSession = new MockHttpSession();
          mockHttpSession.setAttribute("user", sessionUser);
      
      
          //when then
          mockMvc.perform(
                  post("/api/board")
                          .session(mockHttpSession)
                          .contentType(MediaType.APPLICATION_JSON_UTF8)
                          .accept(MediaTypes.HAL_JSON)
                          .content(objectMapper.writeValueAsString(boardRequestDto))
          )
                  .andDo(print())
                  .andExpect(status().is2xxSuccessful())
                  .andExpect(jsonPath("_links.update-board").exists())
                  .andDo(document("create-board", 
                          links(
                                  linkWithRel("profile").description("link to profile"),
                                  linkWithRel("self").description("link to self"),
                                  linkWithRel("update-board").description("link to update an existing board")
                              ),
                          relaxedResponseFields(
                                  fieldWithPath("id").description("identifier of new Board"),
                                  fieldWithPath("boardName").description("name of new Board"),
                                  fieldWithPath("delFlag").description("delFlag of new Board")
                                  )
                          ));
           }
      }
      
    • 테스트 코드 실행 후 생성된 adoc 파일들 (문서조각)



  • 문서 만들기

    • gradle에 맞는 위치에 종합할 문서 파일을 직접 생성한다.(src/docs/asciidoc) https://spring.io/guides/gs/testing-restdocs/

      The default location for Asciidoctor sources in Gradle is src/doc/asciidoc
      
      • 아까 생성한 조각을 사용할 수 있다!

        include::{snippets}/create-board/curl-request.adoc[]
        


    • 생성한 index.adoc 파일을 html로 변환시켜야한다!! 여러 사이트를 참고해서 build.gradle 을 수정해보자

      • https://github.com/spring-projects/spring-restdocs/blob/main/samples/rest-notes-spring-hateoas/build.gradle

      • https://spring.io/guides/gs/testing-restdocs/

      • https://shinsunyoung.tistory.com/85

        plugins {
            id 'org.springframework.boot' version '2.3.8.RELEASE'
            id 'io.spring.dependency-management' version '1.0.11.RELEASE'
            id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
            id 'org.asciidoctor.convert' version '1.5.3' // 추가
            id 'java'
        }
        
        
        group = 'com.toy'
        version = '0.0.1-SNAPSHOT'
        sourceCompatibility = '11'
        
        
        repositories {
            mavenCentral()
            maven { url "https://plugins.gradle.org/m2/" }
        }
        
        
        subprojects {
            group = 'com.toy'
            version = '0.0.1-SNAPSHOT'
        
        
            apply plugin: 'java'
            apply plugin: 'org.springframework.boot'
            apply plugin: 'io.spring.dependency-management'
            apply plugin: 'com.ewerk.gradle.plugins.querydsl'
            apply plugin: 'org.asciidoctor.convert' // 추가
        
        
            sourceCompatibility = 11
        
        
            repositories {
                mavenCentral()
            }
        
        
            dependencies {
                asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor' // 추가
                compileOnly 'org.projectlombok:lombok'
                annotationProcessor 'org.projectlombok:lombok'
                compile("org.mariadb.jdbc:mariadb-java-client")
        
        
                testImplementation('org.springframework.boot:spring-boot-starter-test') {
                    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
                }
                testImplementation('org.springframework.restdocs:spring-restdocs-mockmvc')
                testImplementation 'org.springframework.security:spring-security-test'
                developmentOnly 'org.springframework.boot:spring-boot-devtools'
            }
        
        
                // 추가
            ext {
                snippetsDir = "${buildDir}/generated-snippets"
            }
        
        
            configurations {
                compileOnly {
                    extendsFrom annotationProcessor
                }
            }
        
        
            test {
                outputs.dir snippetsDir // 추가
                useJUnitPlatform()
            }
        
        
            // 추가
            asciidoctor {
                inputs.dir snippetsDir
                dependsOn test
            }
        
        
            def querydslDir = "$buildDir/generated/querydsl"
        
        
            querydsl {
                library = "com.querydsl:querydsl-apt"
                jpa = true
                querydslSourcesDir = querydslDir
            }
            sourceSets {
                main {
                    java {
                        srcDirs = ['src/main/java', querydslDir]
                    }
                }
            }
            compileQuerydsl{
                options.annotationProcessorPath = configurations.querydsl
            }
            configurations {
                querydsl.extendsFrom compileClasspath
            }
        
        
        }
        
        
        project(':module-web') {
            dependencies {
                implementation project(path: ':module-domain', configuration: 'default')
            }
        
        
            // 추가
            task copyDocument(type: Copy) {
                dependsOn asciidoctor
        
        
                from file("build/asciidoc/html5/")
                into file("src/main/resources/static/docs")
            }
        
        
            // 추가
            build {
                dependsOn copyDocument
            }
        }
        
        
        project(':module-batch') {
            dependencies {
                implementation project(path: ':module-domain', configuration: 'default')
            }
        }
        
        
        bootJar { enabled = false }
        
      • gradle build 후 다음 경로에 html 파일이 생성된 것을 볼 수 있으며 profile에 해당 문서를 링크하면 API에 대한 정보, 명세 등을 볼 수 있고, 스스로 설명할 수 있다! (self-descriptive message)


                    

            
[간단히 생성한 html 파일]