본문 바로가기

프로그래밍/세미나

KSUG 2019 하반기 세미나

세선 설명

  1. 더 자바: 코드를 테스트하는 다양한 방법 (백기선 님)
  2. 더 자바: 코드를 조작하는 다양한 방법 (백기선 님)
  3. 자바한정: 널 서바이벌 가이드 1 (박성철 님)
  4. 자바한벙: 널 서바이벌 가이드 2 (박성철 님)
  5. 잘 키운 모노리스 하나 열 마이크로 서비스 안 부럽다 (박용권 님)

소감

C#을 메인으로 사용하고 있는 나에게 KSUG(Korea Spring User Group)에서 하는 세미나는 조금 낮설지만 발표 주제를 자세히 보면 특정 언어에 국한되는 내용은 아닐거라는 생각이들었고 또 최근 스프링 공부를 하면서 몇가지 궁금증도 있어서 세미나에 참석했다.

 

다 들은 시점에서 소감을 쓰자면 나는 보통 세미나를 동기부여 목적에서 참석하고 오는 편인데, 이번 세미나는 내용 측면에서도 많은 도움이 되었다.

 

세션 1, 2는 백기선님의 세션으로 테스트 방법과 자바 코드 조작에 대한 세션이었다. 평소에 백기선님의 온라인 강의를 듣기도 했었고, 최근에 팀에서 프로젝트에 테스트를 넣기 시작해서인지 이번 세미나에서 가장 기대가 되었다. 개인적으로는 DB 테스트에 대해서 이야기를 들은 것은 확실히 고민거리에 답을 주는 것 같아서 많이 도움이 되었고, 팀원들과도 이 부분에 대해서는 공유하고 이야기 할 예정이다. 다만 시간 관계상 내용이 다 진행되지 못한 부분은 너무 아쉬웠다.

 

세션 3, 4의 경우는 '자바 한정'이라는 단어가 제목이 들어가 있는데, 꼭 그렇지는 않았다. null이 왜 만들어졌고, 강연자는 null에 대해서 어떻게 생각하고, 또 null 에 대해서 안전한 프로그래밍을 하려면 어떻게 하는것이 좋은지에 대해서 들을 수 있었다. 진행을 하면서 '오브젝트'책에 대해서 많이 소개가 되었는데, 최근에 관련 책을 읽고 가서 인지 고객을 끄덕이는 부분이 많았다.

 

마지막 세션5는 박용권님 세션으로 기대는 제일 안했지만, 모든 세션을 다 들었을 때 제일 도움이 되는 세션이었다. 발표 시간이 1시간 밖에 안되었다는게 너무 아쉬웠다.

마이크로 서비스로 되어 있는 시스템을 모노리스로 변경하면서 강연자가 생각하는 시스템의 구조와 어떻게 만드는게 좋은지에 대한 내용이었는데, 제목은 저렇게 되어 있지만 실제 핵심은 모듈화와 의존성을 어떻게 관리할 것인지에 대한 부분이었다.

개인적으로 도움이 되었다는 이유는 DDD와 오브젝트 책을 보고 난 뒤에 의존성이나 객체간의 책임 그리고 모듈의 단위에 대해서 고민이 많았었는데, 이 부분에 대해서 강연자의 접근 방법에 대해서 들을 수 있어서 너무 좋았다.
결국 중요한 것은 마이크로 서비스가 Hot하니까 이렇게 구현해야한다 가 아니다. 마이크로 서비스로 만들었더라도 그 의존성이 여전히 남아있고, 각각이 독립적으로 동작하지 않는다면 큰 똥을 작은 똥으로 나눈 것에 불과하다.
이 세션의 내용도 이번주 금요일에 팀원들에게 공유할 예정이고, 우리 코드에 개선 방법에 대해서 논의를 해보려고 한다.


세션 1 - 더 자바: 코드를 테스트하는 다양한 방법

발표 자료: Link

 

Sping Boot 2.2.1

  • JUNit5가 들어감

JUnit5 Test Code

  • DisplayName Annotation 을 이용하면 해당 문자열로 테스트를 보여 준다
    • Class와 Method에 모두 사용
  • assertAll()
    • 하나의 Method에 여러 개의 Assert가 있을 때 중간에 하나가 실패하더라도 여러 개를 다 실행하도록 할 수 있는 기능
  • 예외 던지기
    • assertThrows() 함수를 사용한다
    • 결과를 리턴 받을 수 있어서, 리턴 값을 이용해서 추가적인 작업이 가능하다
  • RunWith
    • SpringBootTest에 ExtendWith가 있어서 Class에 선언하던 RunWith를 생략하고 바로 사용할 수 있다

Mockito

  • 스프링부트 2.2에 자동으로 들어가 있다
    • SpringBootStater 패키지
    • IntelliJ라면 우측에 Gradle에서 패키지 기준으로 볼 수 있다
  • RequairedArgsConstructor Annotation
    • Lombok 라이브러리
    • final로 생성된 변수에 대해서는 생성자를 생성 및 의존성을 자동으로 주입 해준다 (기본 생성자에 대해서)
      • Autowired를 사용해서 의존성을 주입하기 보다는 이 방법이 더 좋지 않을까?
  • Mock 생성
    • 모듈간의 의존성만 알고, 내부 구현을 모르거나 인터페이스만 안다면 목업을 사용하는게 맞다
    • Mock 객체 생성
      • mock(class)를 사용하면 기본 mock을 만들어 준다
    • Annotation
      • class 아래에 Mock annotation으로 선언한다
      • class 에 ExtededWth(MockitoExtesion.class)
  • Stubbing
    • Mock 객체가 생성되었을 때 Mock 객체의 동작을 선언하는 것 (Stub 타입이 따로 있는 것은 아닌 것 같다)
    • when-then return 을 이용해서 동작을 정의하는 것
    • given 절에 속하는 when이 이상하게 보인다면 given-will return을 써 된다 (Mockito.BDDMockito)

테스트 예제

  • 서비스에 대한 테스트
    • Repository를 Mockup한다
  • 테스트 환경
    • 개발 - DB with Docker
    • 테스트 - H2, Mock
    • 운영 - DB
  • 테스트 코드에서 Mock 쓰는 경우
    • 외부 서비스
    • 구현되지 않은 부분을 사용할 때
  • 테스트에서 운영과의 Gap을 줄일 수 있을까?
    • 처음에는 Mock대신 H2를 사용했으나 DB가 다른 문제가 발생한다
    • 이후에는 Docker를 이용해서 실제 테스트 용 DB를 띄운다
      • 각 환경에 따라 Docker 를 실행해야 하는 스크립트가 들어가기 시작한다
      • Mocking을 안해도 되니까 조금은 편리해졌지만…
      • Docker를 켜두고 테스트를 해야 한다
    • TestContainers 라는 컨테이너를 사용
      • 테스트 코드에서 Docker 컨테이너를 띄울 수 있다
      • 방법
        • class에 TestContainers를 선언 (의존성을 추가해야 한다)
        • Container라는 의존성으로 도커이미지를 테스트 코드에서 띄울 수 있다
      • 조금 더 나아가면 운영용 DB의 데이터를 덤프떠서 테스트 DB에 넣어주면 최대한 운영환경에 맞게 테스트가 가능하다
    • 2개 이상의 DB에 대해서는 Docker Compose를 써서 (서비스와 해당 서비스가 참조하는 DB가 있을 때)
      • docker-compose-rule을 사용
    • 그런데 느리다
      • 그래서 개발은 Mock을 사용하고
      • CI/CD에서는 도커를 올려서 테스트한다
    • JUnit5에서 Tag를 사용하면 구분해서 처리할 수 있다

로컬에서 재현하기 힘든 운영 환경 이슈

  • Chaos Monkey for Spring Boot 서비스
    • 레이턴시 , 메모리 누수, 예외, 서비스 죽음 등을 시연해볼수 있다
    • Spring-Boot-Actuator를 이용하면 런타임 중에 설정이 가능하다
    • 설정을 하면 ‘Service’ 에는 자동으로 적용이 되고, 다른 Annotation에도 적용 가능하다
    • 실제 사용 시나리오
      • postman으로 사용중인지 체크
      • 다른 설정 값들도 post요청을 보내면서 설정해 줄 수 있다

세션 2 - 더 자바: 코드를 조작하는 다양한 방법

발표 자료: Link

 

바이트 코드 조작

  • 바이트 코드란
    • 자바 파일을 컴파일 하고 생성된 .class 파일에 들어가 있는 내용
    • Instruction set for JVM
  • 보는 방법
    • javac 파일 경로
      • javac -verbose 경로
    • javap로 열만 보이긴하는데
    • javap -c 경로로 보면 설정 값들이 보인다
  • 바이트 코드 조작은 언제 쓰는가?
    • 스프링 컴포넌트 스캔 → 바이트코드에 붙어 있는 annotation을 찾는다
    • 하이버네이트 ORM 프록시를 사용한 Lazy Loading (Fetchtype에 대해서) → 중간에 프록시를 만들어서 리턴해 준다
    • 특정 함수의 결과 값을 조작할 수 있다
  • JavaAgent
    • ClassFileTransformer 구현체를 JVM에 등록해주는 역할
    • 메인 메서드 호출 전 후에 트랜스포머를 전달할 수 있다
    • 과정
      • 자바 파일 → 클래스 파일 → 로더
      • 자바 파일 → 클래스 파일 → 트랜스포머 → 로더

세션 3, 4 - null 서바이벌 가이드

발표 자료: Link

 

이 발표는 책임을 가지는 코드에 대한 이야기

  • 개발자가 코드를 작성할 때, 자신이 작성하는 코드에 대해서는 책임을 지고 개발해야 한다

null과 null 안정성

  • null 참조의 기원: 레코드 핸들링 (토니호어, 64년 창안, 65년 발표)
    • reference: 레코드 간의 관계를 표현
    • null: 관계가 없음을 나태는 특수한 값
    • 두 참조 값이 null일 때 두 참조는 동일하다고 판단

소프트웨어 결함 통계

  1. Native Crash (161)
  2. NullPointer (149)
  3. Activity Not found (110)

Null 안정성 언어 지원

  1. Null 안전 연산자
    • Null 병합 연산자: 첫번째 인자가 null이면 null을 반환하고 아니면 두번째 인자를 반환
    • Null 조건 연산자: 첫번째 인자가 null이면 null을 반환하고 아니면 두번째 인자의 작업을 실행
  2. Null 안전한 타입 시스템
    • Null 가능 타입: null 기능을 선택적 타입 속성으로 취급
      • 코틀린, C# 8.0 이상
    • Null 타입과 타입 결함
      • null은 Null 타입의 값 : var x:SomeType Null = null
      • 실론, 스칼라3 (예정)
    • Optional 사용 (자바8부터의 문법은 다 몰라서 Optional의 정확한 동작은 알지 못했음 ㅠ)
      • 기본적으로는 불가능
      • 특정 설정을 통해서 가능

null을 이용한 안전한 코딩법

  1. 기본적으로 null을 쓰지 말자
    • 기본적으로 쓰지 말고 필요할 때만 쓰는 것으로 우리가 정하자
    • 컴파일러 확장 기능
      • IntelliJ: ParameterAreNonnullByDefault
  2. null 문맥을 제한된 범위 안에 가두자
    • CS 대 원칙: ‘큰 문제는 제어 가능한 작은 문제로 나누어 정복하고 다시 통합한다’
    • 메서드를 작게 만들자
      • 분해된 것은 또 하나의 완벽한 기능 이어야 한다. 그리고 이들간의 협력이 잘 되어야 한다
    • 좋은 캡슐화
      • 높은 응집성: 한가지 책임을 갖는다 , SRP
        • 모든 필드가 객체와 생애주기가 같다
        • 모든 메서드가 모든 필드를 대상으로 작업한다
      • 낮은 결합
        • 디미터 법칙, ‘묻지말고 시켜라’
          • 디미터 법칙
            • 결국 도트를 하나만 사용하라
            • 예: DDD의 Aggregate (각각의 Service는 AggregateRoot만 의존해야 한다)
          • 인터페이스에 의존
  3. API에 null을 최대한 쓰지 말아라
    • null로 지나치게 유연한 메서드를 만들지 말고 명시적인 메서드를 만들어라
    • null을 반환하지 말라
      • 빈 컬랙션이나 null 객체를 활용하라
      • null을 반환하지 말고 예외를 던져라
    • 파라미터의 경우는 null로 선택적 파라미터를 주지 말고, overload해서 함수를 여러 개 만들어라 (= null을 넘길 일이 없도록 해라)
  4. Null 객체를 활용하다
    • null object pattern: 이종립님 번역 글을 보자
    • Interface를 상속하는 아무것도 하지 않는 기능을 가지는 객체를 리턴한다 → 이러면 아무일도 하지 않으니 그냥 아무것도 안하는 식으로 끝나지 않을까
      • 객채간의 결합도가 최대한 잘 되어 있는 코드에 적용해야 한다
      • 그렇지 않으면 결합도 때문에 아무것도 안하는 객체를 만들수가 없다
  5. Null을 명시적으로 표현하자
    • java.util.Optional 은 값이 없음을 명시적으로 표현하는 컨테이너 객체
    • Optional의 사용법- The Mother of AllBikesheds (동영상 보기)
  6. 계약에 의한 설계를 적용하자 (Design by Contract)
    • API 규약을 소비자와 제공자 사이에 지켜야 할 엄격한 계약으로 여기는 설계 방법
      • 메서드가 실행되기 전과 후에 사전 조건과 사후 조건을 체크해서 정상적인 상태에서 실행되고 있다는 것을 확인한다
    • ‘오브젝트 부록 A’에 있는 걔약에 의한 설계를 보면 된다
    • Java에서는
      • 사실 라이브러리가 별로 없다
      • 스프링 Assert Class
      • Valid4J + hamcrest
      • Assert4J
      • 단정문(assertion) 사용
  7. 구조체에서는 펑터(Functor)를 활용하자
    • 자바는 순수 OOP언어가 아니므로 모두 객체일 필요는 없다
    • 데이터는 객체가 아니므로 (예: DTO), 디미터 법칙을 지킬 필요는 없다
    • 펑터 → 맵 (Optional에 펑터가 있다)
      • 값을 변경해주는 컨테이너
      • 함수형의 컨테이너를 한번 보자
  8. 객체의 기본값을 유용하게 만들자
    • 기본값이 객체를 사용하는데 유용하도록 설정하자. null로 하지말고

null에 안전하다고 점검해주는 도구

  • 도구가 체크해주는 것은 좋지만 중요한 것은 내가 코드를 안전하게 만드는게 중요하다
    1. 정적 분석
      1. JSR 305 → 진행이 되지 않음
      2. 도구
        • 파인드 벅스/스팟 벅스
      3. 인텔리제이 어노테이션
        • ParameterAreNonnullByDefault Annotation 지원
        • Contract : 메서드에서만 지원
      4. 어노테이션 프로세서
        • Nullaway 기본이 not null
      5. 스프링 제공 어노테이션
        • 스프링5에서 지원
        • NotNull, Nullable
        • NotNullAPI, NotNullFields
    2. 타입 시스템 확장
      1. JSR-308 타입 어노테이션
        • 컴파일 시점에 자바의 기본 타입을 확장해서 추가적인 체크를 지원

세션 5 - 잘 키운 모노리스 하나 열 마이크로서비스 안 부럽다

발표 자료: TBD

 

마이크로서비스에서 모노리틱으로 갈아탄 경험

  • 기술부채
  • 운영

기존 서비스의 특징

  1. 데이터 원본을 공유하고 있다
  2. 기능을 수행하는 과정이 지나치게 복잡했다 (서비스 간 호출 때문)
  3. 특정 기능을 분배 하려면 n개 이상의 서비스를 수정 하고 동시에 배포 해야 한다
  4. 이 시스템은 원래 하나였는데, 여러개로 분산했다

기술 부채

  • 개발팀에 비해서 지나치게 많은 마이크로서비스가 있어서 한사람이 여러 서비스를 관리해야했다
  • 이렇게 되면 개발자가 최대한 빨리 수정할 수 있는 곳에 코드를 수정하게 된다

모노리틱과 마이크로서비스는 대결구도가 아니다

  • 아마존과 넷플릭스의 예시를 주로 내새우지만 이게 모든 회사의 규모와 같을까?

아키텍쳐 스타일을 변경하면 기존의 문제가 해결될까?

  • 덩치만 줄인다면 의존성을 가지는 분산된 모노리틱을 만나게 된다
  • 응집과 결합을 다스리는 게 먼저다
    • 응집: 특정 행위를 변경할 때 한곳에서 일어나는것 → 응집도가 높다 / 응집도가 낮다
    • 결합: 하나의 변경이 여러곳에 변경을 요하는 정도 → 결합도가 높다 / 결합도가 낮다

마이크로 서비스를 잘 적용하기 위해서는 분산 시스템의 문제를 함께 해결해야 한다

 

잘 만들어진 모노리틱

  • 로컬 마이크로 시스템
  • 좋은 아키텍쳐라면 모노리틱 구조로 만들어지라도 독립적 배포 가능 시스템으로 구현되어야 하고, 그 역순으로 했을 때도 구현이 가능해야 한다(클린아키텍쳐에 나와있음)

“모노리틱을 잘 못 만드는 팀은 마이크로서비스도 잘 못 만들 것이다”

 

참조 - 헥사 기반 아키텍쳐 (포텐 어앱터 패턴)

 

적용 경험 예시 by code

  • 도메인 중심 모듈 구성으로 강한 응집도 얻어내기
    • 도메인 중심으로 구성을 구분하고 그 안에서 의미를 가지는 계층형을 가져야 한다
    • 그리고 도메인 사이에는 의존성 관리로 느슨하게 만들어 준다
  • 의존성
    • 의존성의 방향을 한방향으로 하려고 고민을 했고 구현을 했다 (의존성 역전으로 방향을 일치화 했다)
  • 모듈과 모듈 사이에 선 긋기
    • 적절한 가시성을 통해서 모듈 보호하기
    • 빌드도구 멀티 프로젝트(Gradle) 구성으로 의존관계를 끊어놓기
    • 전체 모듈의 관계를 알고 있는 하나의 모듈이 있고, 이 모듈이 서로를 이어준다
    • 컨텍스트를 통한 선 긋기
      • 스프링의 빈은 기본적으로 전역이기 때문에 그냥 참조를 할 수 있다
      • 루트 컨텍스트를 기반으로 분류(=카탈로그 컨텍스트)를 해주어서, 런타임에 에러가 나도록 한다

기타 - 참고하면 좋은 자료

  • [우아한테크세미나] 우아한객체지향 by 조영호님: Link
  • Optional - The Mother of All Bikesheds by Stuart Marks: Link