프로젝트 주제; 통합 결제 모듈 클론코딩

아키텍처; MSA 기반 멀티 모듈? + DDD 구조

구현해야 기능;

우리가 구현해야 하는 기능을 구현할 때, 우리는 단순하게 기능만 구현하지 않을 것이다. 첫 번째로, 작성된 코드를 보면서 현대식 자바 문법에 대해서 자세하게 알아 볼 것이다. 단순히 자바 문법을 배운다고 자바를 잘 작성할 수 있는 것은 아니기 때문에, 자바를 Java스럽게 작성하는 방법에 대해서 이야기를 함께 나눠보자.

두 번째로, 우리가 무의식적?으로 사용하는 Spring Boot에 대한 어노테이션나 기술들을에 대해서 자세하게 살펴 볼 것이다. 무엇을 통해서?! 코드와 디버깅을 통해서 매우 딥하게 살펴 볼 것이다. 두 번째 수업에서의 최종 목표는 기술 면접 때 자주 나오는 문제들을 단순히 외운 내용으로 설명하는 것이 아니라, 이해를 바탕으로 대답을 함으로써 예측 불가능한 질문까지 생각해서 답을 찾아낼 수 있는 능력을 배양하는 것이다.

image.png

세 번째로, 객체지향 관점에서 프로젝트 구조를 설계를 왜 이렇게 했고, 어떤 구조로 비즈니스 로직을 추상화 했는지 알아보자. 이때 우리가 자주 사용 및 거론되는 기술은 아마도 접근 제한자추상화, 인터페이스일 것 같은데, 생각보다 우리는 이것들에 대해서 상세하게 잘 모른다. 그래서 객체지향스러운? 코드를 함께 보면서 이야기를 나눠보자.

Hip스러운 Code; Effective + Morden Style

의미 있는 코드 Style

1. public Constructor vs Static Factory Method

1.1 정적 팩토리 메서드를 이용한 인스턴스 생성

1.1.1 장점

첫 번째, 이름(Naming)을 가질 수 있다.

생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 반면 정적 팩토리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.

두 번째, 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.

불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.[Singleton]

세 번째, 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

네 번째, 입력 매개변수에 따라 매번 다른 클래스의 인스턴스를 반환 할 수 있다.

다섯 번째, 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

1.1.2 단점

첫 번째, 상속할 수 없다.

두 번째, 정적 팩토리 메서드 찾기가 어렵다.

명명 규칙

from; Date d = Date.from(instant);

매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드

of; Set<Developer> dev = EnumSet.of(YOO, JIN, HO);

여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드

valueOf; Boolean isTrue = Boolean.valueOf(”true”);

예제 코드

PaymentRequestDto 클래스

@Getter
public class PaymentRequestDto {
    private final String pgCorpName;

    private PaymentRequestDto(String name) {
        this.pgCorpName = PgCorp.valueOf(name.toUpperCase()).toString().toLowerCase();
    }

    public static PaymentRequestDto of(String pgCorpName) {
        return new PaymentRequestDto(pgCorpName);
    }
}

2.1 new 키워드를 이용한 인스턴스 생성

2. Builder 패턴

2.1 생성자와 정적 팩토리의 한계

선택적 매개변수가 많을 때 정적 팩토리와 생성자는 적절히 대응하기 어렵다.

왜? 첫 번째는, 메서드를 호출하는 과정에서 파라미터를 할당할 때 매개변수의 순서를 틀리거나 누락할 수 있다는 휴먼 에러를 내포하고 있다.

두 번째는, 가독성이 매우 현저하게 떨어진다. 계속하는 이야기이지만, 개발자의 실무는 새로운 코드를 작성하는 것이 아니라, 기존 코드를 읽고 fxxk를 외치는 것이 주요 업무이다. 그래서 좋은 개발자 || 휼륭한 개발자는 읽고 싶은 코드를 작성하는 사람이지 않을까 싶다. 그런데, 선택적 매개변수(Parameters)가 많은 경우, 생성자 관련 메서드를 이용한 인스턴스 생성은 매우 협오스럽다. 왜? 해당하는 파라미터가 어떤 값을 의미하는지 전혀 알 수 없기 때문이다.

public class PaymentApproveMessage {
    private String mId;
    private String currency;
    private String method;
    private String lastTransactionKey;
    private String paymentKey;
    private int totalAmount;
    private int balanceAmount;
    private int suppliedAmount;
    private int taxFreeAmount;
    private String requestedAt;
    private String approvedAt;

    public PaymentApproveMessage(String mId, String currency, String method, String lastTransactionKey, String paymentKey,
                                 int totalAmount, int balanceAmount, int suppliedAmount, int taxFreeAmount, String requestedAt,
                                 String approvedAt) {
        this.mId = mId;
        this.currency = currency;
        this.method = method;
        this.lastTransactionKey = lastTransactionKey;
        this.paymentKey = paymentKey;
        this.totalAmount = totalAmount;
        this.balanceAmount = balanceAmount;
        this.suppliedAmount = suppliedAmount;
        this.taxFreeAmount = taxFreeAmount;
        this.requestedAt = requestedAt;
        this.approvedAt = approvedAt;
    }

}

2.2 Builder 패턴으로 문제 해결

왜 생성자를 private || protected로 선언하는가? protected는 같은 패키지 내에서만 사용하거나 상속이 가능함. private은 해당 클래스 내에서만 호출이 가능하다.

이렇게 선언하게 되면 인스턴스 생성하는 방식 강제화 할 수 있고 불완전한 객체를 만들어내서 오류를 발생 시키는 일 또한 막을 수 있다. effective-java에서는 기본 생성자의 접근 제한자를 무조건 public으로 하는 것을 지양하라고 이야기하고 있다. 왜냐하면 불완전한 객체를 만들어내기 때문이다. 이러한 문제로 오류가 발생하면 생각보다 해당하는 오류 찾기가 쉽지 않다.

3. 불변 객체

3.1 불변식; Invariant

프로그램이 실행되는 동안 또는 정해진 기간 동안 반드시 만족해야 하는 조건을 말한다. 다시 말해 변경을 허용할 수 있으나, 주어진 조건 내에서만 허용한다는 뜻이다. 예를 들면, **“리스트(List)의 경우, size의 크기는 반드시 0 이상이어야 하고 한 순간이라도 음수 값이 될 수 없다.”**라는 조건식이 List.size()의 불변식이다.

3.2 불변 클래스 선언하는 방법

3.2.1 final 클래스

3.2.2 클래스의 모든 필드 final로 선언 한다. 단, 예외도 존재한다.

설계자의 의도를 명확히 드러내는 방법 중 가장 좋은 방법이다. 클라이언트(정의된 클래스를 인스턴스로 생성해서 사용하는 주체)는 주적이다. 이들은 항상 설계자의 의도에서 벗어나는 행동을 한다. 그렇기 때문에 행동에 제약을 걸어야 한다.

3.2.3 private 필드

3.2.4 클래스의 확장(extends)를 막는다; only private 생성자와 Static Factory

public || protected 범위로 생성자를 선언하면 상속(extends)이 가능하다. 그리고 생성자의 접근 제한자를 통해서 클라이언트는 해당 클래스가 상속에 대한 확장을 염두에 두고 설계가 되었다고 생각할 수 있다. 하지만, 설계자는 한 가지 놓친 사실이 하나 있다. 클라이언트는 설계자의 의도대로 행동하지 않는다라는 것이다. 그래서 상속을 받은 하위 클래스에서 부주의하게 혹은 나쁜 의도로 객체의 상태를 변하게 할 수 있다. 그렇게 되면 불변성 또한 깨지게 된다.

이러한 문제는 Only private 생성자 || Static 팩토리 메서드를 통해서 막을 수 있다.