2024년 1월 13일 토요일

2023년 4월 16일 일요일

테스트 관련 메모

테스트에 대해 헷갈리는 내용이 많음. 테스트 환경을 잘 만드는 것도 중요할 듯 하다.

  • chatGPT

    Java Spring 환경에서 사용할 수 있는 대표적인 테스트 프레임워크는 다음과 같습니다.
    
    JUnit: Java 언어로 작성된 단위 테스트 프레임워크입니다. Spring에서도 가장 많이 사용되는 테스트 프레임워크 중 하나입니다.
    
    Mockito: Mock 객체를 생성하고 관리하는 라이브러리로, Spring에서 유닛 테스트를 작성할 때 자주 사용됩니다.
    
    Spring Test: Spring Framework에서 제공하는 테스트 프레임워크입니다. Spring 애플리케이션의 통합 테스트, 웹 애플리케이션의 테스트 등을 작성할 때 사용됩니다.
    
    TestNG: JUnit과 유사한 기능을 제공하는 테스트 프레임워크로, Spring에서도 사용 가능합니다. 테스트 그룹, 의존성 설정 등 다양한 기능을 제공합니다.
    
    Selenium: 웹 애플리케이션의 UI 테스트를 자동화하는 데 사용되는 프레임워크입니다. Spring에서도 웹 애플리케이션의 UI 테스트를 작성할 때 사용됩니다.
    
    Cucumber: BDD(Behavior Driven Development)를 지원하는 테스트 프레임워크입니다. 비즈니스 요구사항을 기반으로 시나리오를 작성하고, 이를 테스트 코드로 변환하는 방식으로 작성합니다.
    
    Arquillian: Java EE 애플리케이션의 통합 테스트를 위한 프레임워크입니다. Spring과 함께 사용할 수 있습니다.
    
    이외에도 다양한 테스트 프레임워크들이 있지만, 위에 소개한 프레임워크들은 Spring 환경에서 가장 많이 사용되는 대표적인 프레임워크들입니다.

용어 정리

  • 자세한 사용법은 패스. 모두 다양한 사용방법을 제공함

Junit

  • 유닛 테스트 프레임워크

  • 흔히 사용하는 @Test

  • 스프링 부트 2.2.x 이상 부터는 Junit5

  • Junit5 는 세 가지 모듈로 구성

    • Junit Platform
    • Junit Jupiter
    • Junit Vintage : Junit4 등 이전 버전의 테스트를 실행할 수 있도록 지원
    • import org.junit.jupiter.api.Assertions;
      import org.junit.jupiter.api.Test;

      class JunitTest {

      @Test
      void test1() {
      String a = "1";
      int number = 1;

      Assertions.assertEquals(Integer.parseInt(a), number);
      }
      }

Mockito

  • 단위테스트를 작성할 때, 모든 의존성을 직접 만들지 않고도 코드를 테스트할 수 있도록 도와줌

    • Mockito 를 이용하여 실제 객체를 모방한 가짜 객체 생성이 가능함
    • Stubbing 을 통해 mock 객체의 메소드를 실행했을 때 어떤 리턴 값을 리턴할 지 정의 가능
    • import org.junit.jupiter.api.Assertions;
      import org.junit.jupiter.api.Test;

      import static org.mockito.ArgumentMatchers.any;
      import static org.mockito.Mockito.*;

      class MockitoTest {
      /**
      * 예시를 위한 테스트
      * 이 테스트에서는 db 에 저장되는 로직이 아니라 다른 로직이 동작하는 지 확인하고 싶음
      * service 의 save 가 호출되면 count 가 증가된다. 이 count 만 정상 동작하는 지 확인하고 싶음
      */
      @Test
      void memberService_count_test() {
      Member member = new Member("name");
      MemberRepository memberRepository = mock(MemberRepository.class);
      when(memberRepository.save(any())).thenReturn(member);

      // mock repository 주입
      MemberService memberService = new MemberService(memberRepository);

      MemberDto dto = new MemberDto("name");
      MemberDto dto2 = new MemberDto("name2");

      Member saveMember = memberService.save(dto);
      Member saveMember2 = memberService.save(dto2);

      // dto2 에 name2 를 저장했지만, mock repository 에 의해 name 가 됨
      Assertions.assertEquals(saveMember.getName(), dto.getName());
      Assertions.assertEquals(saveMember2.getName(), dto.getName());

      // mock 객체에 대해서 발생한 동작에 대해서 확인 가능
      verify(memberRepository, times(2)).save(any());

      // save 를 호출하였을 때 count 가 증가되는 지 확인한다
      Assertions.assertEquals(memberService.getCount(), 2);
      }
      }
    • // 어노테이션으로 사용
      @ExtendWith(MockitoExtension.class)
      class MockitoTest2 {

      @Mock
      private MemberRepository memberRepository;

      @InjectMocks
      private MemberService memberService;

      /**
      * 예시를 위한 테스트
      * 이 테스트에서는 db 에 저장되는 로직이 아니라 다른 로직이 동작하는 지 확인하고 싶음
      * service 의 save 가 호출되면 count 가 증가된다. 이 count 만 정상 동작하는 지 확인하고 싶음
      */
      @Test
      void memberService_count_test() {
      Member member = new Member("name");
      when(memberRepository.save(any())).thenReturn(member);

      MemberDto dto = new MemberDto("name");
      MemberDto dto2 = new MemberDto("name2");

      Member saveMember = memberService.save(dto);
      Member saveMember2 = memberService.save(dto2);

      // dto2 에 name2 를 저장했지만, mock repository 에 의해 name 가 됨
      Assertions.assertEquals(saveMember.getName(), dto.getName());
      Assertions.assertEquals(saveMember2.getName(), dto.getName());

      // mock 객체에 대해서 발생한 동작에 대해서 확인 가능
      verify(memberRepository, times(2)).save(any());

      // save 를 호출하였을 때 count 가 증가되는 지 확인한다
      Assertions.assertEquals(memberService.getCount(), 2);
      }
      }

@SpringBootTest / @WebMvcTest

  • @SpringBootTest

    • 통합테스트 느낌
    • 프로젝트 내부에 있는 스프링 빈을 모두 등록하여 테스트에 필요한 의존성 추가
    • 실제로 구동되는 애플리케이션과 거의 동일한 환경을 제공
    • 단위 테스트가 아닌 Spring Framework 에서 전체적으로 Flow가 제대로 동작하는지 검증하기 위해 사용
    • 애플리케이션의 모든 계층(데이터베이스, 서비스, 컨트롤러 등)을 테스트할 수 있음
    • 시간이 오래걸림
    • // 많이 보이는 테스트로 그냥 reference 예제 사용
      @SpringBootTest
      @AutoConfigureMockMvc
      class MyMockMvcTests {
      // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
      @Test
      fun testWithWebTestClient(@Autowired webClient: WebTestClient) {
      webClient
      .get().uri("/")
      .exchange()
      .expectStatus().isOk
      .expectBody<String>().isEqualTo("Hello World")
      }

      }
  • @WebMvcTest

    • Controller 클래스를 테스트하기 위한 어노테이션
    • 애플리케이션의 다른 구성요소(service, repository) 를 모두 로드하기 않고도 특정 컨트롤러를 테스트할 수 있도록 함
    • 시간이 짧고, 불필요한 의존성을 로드하지 않음
    • // 많이 보이는 테스트로 그냥 reference 예제 사용
      @WebMvcTest(UserVehicleController.class)
      class MyControllerTests {

      @Autowired
      private MockMvc mvc;

      @MockBean
      private UserVehicleService userVehicleService;

      @Test
      void testExample() throws Exception {
      given(this.userVehicleService.getVehicleDetails("sboot"))
      .willReturn(new VehicleDetails("Honda", "Civic"));
      this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
      .andExpect(status().isOk())
      .andExpect(content().string("Honda Civic"));
      }

      }

WireMock

  • HTTP 기반의 웹 서비스를 mock 하는 도구
    • 특정한 HTTP 요청에 대한 응답으로 미리 지정해둔 형태의 HTTP 응답이 반환됨
  • chatGPT : WireMock은 언제 사용하면 좋을까?

    WireMock은 주로 다음과 같은 상황에서 사용됩니다.
    
    1. 외부 API 호출 테스트
    API를 사용하는 애플리케이션을 개발할 때, 외부 API 호출을 테스트하기 위해서는 외부 API 서버가 필요합니다. 그러나 외부 API 서버가 불안정하거나, 인증이 필요한 경우에는 테스트를 진행하기 어렵습니다. 이런 경우에는 WireMock을 사용하여 가짜 응답을 생성할 수 있습니다.
    
    2. 마이크로서비스 아키텍처에서의 서비스 통합 테스트
    마이크로서비스 아키텍처에서는 각각의 서비스가 독립적으로 개발되고, 운영됩니다. 이때 서비스 간의 통합 테스트를 진행해야 합니다. WireMock은 마이크로서비스 아키텍처에서 서비스 통합 테스트를 위한 훌륭한 도구입니다.
    
    3. 데모 시스템 구축
    WireMock은 미리 정의된 응답을 가지고 있기 때문에, 데모 시스템을 구축할 때 유용합니다. 예를 들어, WireMock을 사용하여 가짜 결제 시스템을 만들고, 이를 사용하여 결제 처리 흐름을 시뮬레이션할 수 있습니다.
    
    4. 테스트 용이성 개선
    WireMock을 사용하면 테스트 코드를 더욱 쉽게 작성할 수 있습니다. 예를 들어, WireMock을 사용하여 가짜 서버를 띄우고, 이를 이용하여 HTTP 요청을 보내는 코드를 작성할 수 있습니다. 이렇게 작성된 테스트 코드는 외부 서버와의 의존성을 제거할 수 있습니다.
  • 이를 활용하여 외부 서버 통신이 필요한 로직의 테스트의 경우

    • 아래와 같이 url, request, response 를 미리 정의하여 해당 값으로 테스트할 수 있음
    • 외부데이터와의 의존도를 낮출 수 있음
  • json 형태를 미리 정의하거나 WireMock.stubFor 을 통해서도 정의 가능

  • // test/resources/mappings/test.json
    {
    "request" : {
    "url" : "/request",
    "method" : "GET"
    },
    "response" : {
    "status" : 200,
    "body" : "Hello Response1",
    "headers" : {
    "Content-Type" : "text/plain"
    }
    }
    }
  • import com.github.tomakehurst.wiremock.client.WireMock;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
    import org.springframework.http.HttpStatus;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.web.client.RestTemplate;

    import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
    import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;


    @SpringBootTest
    @ActiveProfiles("test")
    @AutoConfigureWireMock(port = 9999)
    class WireMockTest {

    private RestTemplate restTemplate = new RestTemplate();

    @Test
    void test_json() {
    String body = "Hello Response1";

    String response = restTemplate.getForObject("http://localhost:9999/request", String.class);

    Assertions.assertEquals(response, body);
    System.out.println(response);
    }

    @Test
    void test_wireMock_method() {
    String body = "Hello Response2";
    stubFor(WireMock.get(urlEqualTo("/request2"))
    .willReturn(
    WireMock.aResponse()
    .withStatus(HttpStatus.OK.value())
    .withHeader("Content-Type", "application/json")
    .withBody("Hello Response2")
    ));

    String response = restTemplate.getForObject("http://localhost:9999/request2", String.class);

    Assertions.assertEquals(response, body);
    }
    }


Testcontainers

  • 테스트 시에 도커 컨테이너를 이용하여 필요한 외부 시스템을 쉽게 생성하고 테스트하는 라이브러리
    • 데이터베이스나 메시지 큐 같은 외부 시스템을 테스트할 때 활용 가능
    • docker-compose 도 사용 가능!
    • 테스트 시에만 도커 컨테이너가 띄워진다고 생각하면 될 듯
    • 하지만 띄우는 시간이 걸림. 테스트가 느려짐. 적절한 용도에 사용하자

// build.gradle
...
testImplementation "org.testcontainers:testcontainers:1.18.0"
testImplementation "org.testcontainers:junit-jupiter:1.18.0"
testImplementation "org.testcontainers:mariadb:1.18.0"
...

// application-test
...
datasource:
url: jdbc:tc:mariadb:10://to-do-list-test
username: admin
password: 1234
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
...

import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.io.File;


@Testcontainers
public class TestContainer {

@Container
public static JdbcDatabaseContainer mariaDBContainer = new org.testcontainers.containers.MariaDBContainer("mariadb:10")
.withDatabaseName("to-do-list-test")
.withUsername("admin")
.withPassword("1234")
//.withConfigurationOverride("conf.d") // DB 서버 추가 설정
.withInitScript("testContainer/initData.sql") // 초기 데이터
;


@Container
static DockerComposeContainer dockerComposeContainer =
new DockerComposeContainer(new File("src/test/resources/testContainer/docker-compose.yml"));

}

(테스트 실행 시 docker ps / docker-compose 에 redis-latest 해놓음 )


참고

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년엔 재밌는 공부 많이하고, 좋은 책들 많이 읽자! (일단 오브젝트 책부터 ...!)