caffeine-library / pro-spring-5

🌱 전문가를 위한 스프링5를 읽는 스터디
5 stars 0 forks source link

[question] 구성 인자(configuration parameter) #23

Closed JasonYoo1995 closed 3 years ago

JasonYoo1995 commented 3 years ago

질문

예제 3-10 코드에서 getter/setter의 input/output인 'String'이 구성 인자(configuration parameter) 정보를 의미한다고 볼 수 있을까요? 그리고 구성 인자는 구현체에서 구현될 필드 그 자체라고 볼 수 있을까요?

상세 내용

image image

연관 챕터

10

cc. @caffeine-library/readers-pro-spring-5

wooyounggggg commented 3 years ago

책의 정의대로라면 구성 인자로 분류할 수 있을 것 같습니다.

구성 인자는 구현체에서 구현될 필드 그 자체라고 볼 수 있을까요?라는 질문은 의도가 잘 이해되지 않아서 조금 더 구체적으로 질문해주시면 좋을 것 같아요!

여담으로, 물론 예시이기 때문에 특이 케이스를 제시했겠지만, 개인적으로 책의 NewsletterSender라는 인터페이스에서 smtp와 관련된 로직을 가지고 있는 것은 좋지 않은 설계라고 생각이 되긴 하네요.

어떠한 프로토콜을 통해 news letter를 전달할 지 모르는 상황인데 SMTP와 결합도 높은 sender를 만들어서는 안 된다는 생각이 들었습니다.

다시 보니 이런 부분도 보이네요 ㅎㅎ

JasonYoo1995 commented 3 years ago

아래와 같은 경우를 얘기한 것이었습니다.

public class NewsletterSenderImpl implements NewsletterSender {
    String smtpServer; // 필드 = 구성인자
    String fromAddress; // 필드 = 구성인자
    void setSmtpServer(String smtpServer){
        this.stmpServer = stmpServer;
    }
    String getSmtpServer(){
        return this.smtpServer;
    }
    void setFromAddress(String fromAddress){
        this.fromAddress = fromAddress;
    }
    String getFromAddress(){
        return this.fromAddress;;
    }
}

그리고 언급하신 좋지 않은 설계라는 것을 다른 표현으로 말하자면, "news letter라는 책임과 stmp라는 책임, 이 2가지 책임을 하나의 클래스가 담당하고 있는 것은 단일 책임 원칙(SRP)을 위반하는 것이므로 좋지 않은 설계다"라고 말할 수 있겠네요

참고

stmp를 대체할 수 있는 다른 대표적인 프로토콜엔 뭐가 있을까요?

wooyounggggg commented 3 years ago

단일책임 원칙에 위배될 뿐더러, NewsletterSender의 추상화 레벨이 그 기능에 비해 너무 높다고 생각이 되었습니다.

다시 말해 NewsletterSender는 이미 기능이 구체적이라서 클래스로 정의하는 것이 맞다고 봅니다.

클래스로 정의해야 할만큼 specific한 녀석을 인터페이스로 정의하다보니, 책에서 구성 인자라는 말로 구구절절 보충 설명을 해야 하는 것 같구요.

좀 더 추상적인 개념인 Sender를 인터페이스로 추상화해서 빼내고, SMTP 기능의 책임을 갖는 SMTPSender 클래스가 그 인터페이스를 구현하고,

NewsletterSender가 SMTPSender를 사용하는 클라이언트로 정의한다면 계층이 알맞게 구성되어서 책임이 기능에 맞게 분리될 수 있을거라고 생각합니다.

제 생각을 담아 아래와 같이 리팩토링 해보았습니다.

public interface Sender {
    public void send();
}
public class SMTPSender implements Sender {

    String smtpServer;

    String fromAddress;

    void setSmtpServer(String smtpServer) {
        this.smtpServer = smtpServer;
    }

    String getSmtpServer() {
        return this.smtpServer;
    }

    void setFromAddress(String fromAddress) {
        this.fromAddress = fromAddress;
    }

    String getFromAddress() {
        return this.fromAddress;
    }

    @Override
    public void send() {
        ...
    }

}
public class NewsletterSender {

    private Sender sender; // sender 의존성 주입

    /// Some NewsletterSender specific feature
    ...
}

설계는 주관적인 측면이 늘 존재하니 피드백이나 다른 의견 있으시면 공유해주시면 좋을 것 같습니다 😄

binchoo commented 3 years ago

구성인자는 구현체에서 구현될 필드 그 자체인가.

질문의 두 번째 명제로 돌아와 보면, 참이 아닙니다. 필드라는 용어가 객체의 멤버의 다른 말일 뿐이므로 그렇게 가정합니다.

구성인자는 필드로 구현되는가? (X)

질문의 의도는 "구성 인자는 구현할 때 어떻게 되나요?" 일까요?

  1. 구성 인자는 외부 파일에서 설정 하는 게 자주 보는 패턴이고, 프로그램의 실행 인자로 넘겨주거나, 가장 안 좋은 방법으로 코드에서 설정 정보를 구성하는 방식이 있습니다. ex) 인공지능 모델의 설정을 JSON파일로 작성

    {
    "architectures": [
    "RobertaForMaskedLM"
    ],
    "attention_probs_dropout_prob": 0.1,
    "bos_token_id": 0,
    "eos_token_id": 2,
    "hidden_act": "gelu",
    "hidden_dropout_prob": 0.1,
    "hidden_size": 1024,
    "initializer_range": 0.02,
    "intermediate_size": 4096,
    "layer_norm_eps": 0.00001,
    "max_position_embeddings": 514,
    "model_type": "roberta",
    "num_attention_heads": 16,
    "num_hidden_layers": 24,
    "pad_token_id": 1,
    "type_vocab_size": 1,
    "vocab_size": 50265
    }

    ex) 스프링에서 객체의 의존 요구사항을 XML 파일로 작성 ex) 도커 컴포즈에서 서비스가 실행할 컨테이너를 YAML 파일로 작성

  2. 설정 정보를 '객체의 필드로서 저장한다'는 것은 정보를 로드하는 방식 중 하나일 것입니다. (추가) 하지만 가장 유용한 패턴을 생각해 본다면.. 구성 인자는 필드에 기억될 것입니다. (DI를 지키는 코드)

    1. 필요한 모든 정보를 추상화한 @Value
    2. 외부 파일에서 구성하여
    3. 프레임워크로부터 주입 받아 필드에 기억.

엔터티 vs 밸류

emiling commented 3 years ago

의존성

의존성에 대한 정의

Screen Shot 2021-07-25 at 12 23 01 AM

출처 : 190620 우아한객체지향 by 우아한형제들 개발실장 조영호님

구성인자(Configuration Parameter)

구성인자라는 말이 결국 영어로 풀게되면 Configuration Parameter이고 말 그대로 설정을 위해 필요한 파라미터라는 의미이므로 의존성을 구성하기 위해 필요한 정보라고 이해하는 것이 좋을 것 같습니다.

특징

예제

아래 파일의 경우 스프링에서 데이터베이스를 사용할 때 필요한 구성 인자들입니다. 설정 정보는 주로 아래와 같이 관리합니다.

# application.yaml 
spring.profiles.include=real-oauth
spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true
spring.session.store-type=jdbc

구성인자는 의존성인가?

책에서도 이에 대한 대답을 애매하게 하고 있습니다. 구성인자를 의존성의 특수한 형태로 볼 수 있다고 하는 한편 SMTP 서버 주소와 이메일의 발신 주소 같은 구성 인자가 실제로는 의존성이 아니라고도 합니다.

제 나름대로 결론을 내리면 구성인자 자체는 의존성이라고 볼 수 없으나 해당 구성인자가 컴포넌트 내의 비지니스 로직이 동작하는 과정에서 사용된다면 의존성을 갖기 시작한다고 볼 수 있을 것 같습니다.

결론

❓ 예제 3-10 코드에서 getter/setter의 input/output인 'String'이 구성 인자(configuration parameter) 정보를 의미한다고 볼 수 있을까요?

예제 3-10 코드에서는 gettersetter를 통해 받아오는 'String'은 SMTP 서버 주소와 이메일의 발신 주소라는 수동적이고 단순한 정보 자체를 의미하므로 구성 인자입니다.

Although you shouldn’t always place setters for dependencies in a business interface, placing setters and getters for configuration parameters in the business interface is a good idea and makes setter injection a valuable tool.

책에서는 비지니스 인터페이스에 의존성을 위한 setter를 선언하지는 말되 구성인자를 위한 settergetter를 두는 것은 좋은 방법이라고 얘기합니다. 따라서 그에 대한 예시로 NewsletterSender 라는 비지니스 인터페이스에 실질적 의존성이 아닌 두 정보에 대한 settergetter를 정의하고 있으므로 구성 인자라고 보는 것이 타당합니다.

❓ 그리고 구성 인자는 구현체에서 구현될 필드 그 자체라고 볼 수 있을까요?

구현될필드라는 부분을 좀 더 구체적으로 생각해봐야할 필요가 있어보입니다.

구현될

NewsletterSender 에서 gettersetter를 통해 SMTP 서버 주소와 이메일의 발신 주소를 받아오도록 함수가 정의되어 있으므로 이에 대한 구현체인 NewsletterSenderImpl에서는 두 정보를 가지고 있거나 적어도 접근 가능해야합니다

즉, 구현된다는 의미를 좀 더 살펴보면

  1. 구현체에서 비지니스 로직을 구현하기 위해선 구성인자를 직접적 혹은 간접적으로 알 수 있어야 한다는 뜻이 되겠고
  2. 이는 결국 구현체에서 두 구성인자에 대해 의존성을 갖게된다는 뜻입니다.

필드

그렇다면 이러한 구성인자는 단순히 필드를 통해서만 의존성을 갖게 될까요?

위 예제의 경우 책에 NewsletterSenderImpl라는 구현체가 직접 정의되어 있지 않으므로 저자의 의도는 잘 모르겠지만 저라면 아래와 같은 방법들을 사용할 것 같습니다.

  1. @Value를 사용한 단순 값 주입

    public class NewsletterSenderImpl implements NewsletterSender {
        // smtpServer와 fromAddress에 대한 정보는 application.yaml에 정의되어야 함
        @Value("${custom.smtpServer}")
    String smtpServer;
        @Value("${custom.fromAddress}")
    String fromAddress;
        ...
    }
  2. Auto Configuration

    # application.properties
    custom.smtpServer=135.246.357
    custom.fromAddress=123.456.789

단 이 경우 이를 위한 custom auto configuration을 별도로 구축해주어야 하므로 외부 라이브러리의 경우 이러한 방식을 사용하기 용이하지만 자체적으로 구축하려면 다소 번거롭습니다. 링크

  1. @ConfigurationProperties등을 통한 다른 객체에게 책임 위임

    @Configuration
    public class DataSourceConfig {
    ...
    @Primary
    @Bean(name = "jpaProperties")
    @ConfigurationProperties(prefix = "spring.jpa")
    public JpaProperties jpaProperties() {
        return new JpaProperties();
    }
    ...
    }
  2. 하드코딩

    public class NewsletterSenderImpl implements NewsletterSender {
    String smtpServer = "135.246.357"
    String fromAddress = "123.456.789"
        ...
    }