Fall in IT.

DIP(Dependency Inversion Principle)에 관하여 본문

기타

DIP(Dependency Inversion Principle)에 관하여

D.Y 2024. 3. 9. 21:04

Go에서 의존성 역전 원칙(DIP) 활용하기

현대 소프트웨어 개발 환경에서는 유연하고 유지 관리가 가능한 애플리케이션을 설계하는 것이 그 어느 때보다 중요합니다. 이를 달성하기 위한 한 가지 핵심은 객체 지향 설계를 안내하는 SOLID 원칙의 초석인 종속성 역전 원칙(DIP)을 이용하는 것입니다. 이 게시물에서는 DIP에 대해서 알아보겠습니다.

 

종속성 역전 원리란 무엇입니까?

DIP의 핵심은 소프트웨어 모듈을 분리하는 것을 목표로 합니다. 이 원칙은 두 가지 주요 아이디어를 주장합니다.

  1. 상위 모듈은 하위 모듈에 의존해서는 안 됩니다. 대신, 둘 다 추상화(인터페이스 또는 추상 클래스)에 의존해야 합니다.
  2. 추상화는 세부사항에 의존해서는 안 됩니다. 오히려 세부 사항은 추상화를 따라야 합니다.

DIP를 준수함으로써 소프트웨어 설계자는 낮은 수준의 모듈 변경(예: 이메일 전송 서비스 전환)으로 인해 높은 수준의 비즈니스 로직을 변경할 필요가 없는 시스템을 만들 수 있습니다.

 

실제 적용: 두 명의 이메일 발신자 이야기

실제 DIP를 설명하기 위해 이메일을 보내는 애플리케이션을 고려해 보겠습니다. DIP가 없는 초기 접근 방식에 어떤 제한이 발생하는지, 그리고 DIP를 적용하면 더욱 유연한 아키텍처가 어떻게 제공되는지 살펴보겠습니다.

 

DIP가 없는 시나리오: 직접 SMTP 접근 방식

처음에 우리 애플리케이션은 이메일을 보내기 위해 SMTP 클라이언트를 직접 사용합니다. 애플리케이션의 비즈니스 로직과 SMTP 클라이언트 구현 간의 긴밀한 결합으로 인해 다음과 같은 몇 가지 과제가 발생합니다.

package main

import "fmt"

// SMTPClient is a low-level module directly used by the application
type SMTPClient struct {
	Server string
}

func (client *SMTPClient) SendEmail(to, subject, body string) {
    fmt.Printf("Sending email via SMTP to %s with subject: %s\n", to, subject)
    // Assume code to send email over SMTP here.
}

func main() {
    smtpClient := SMTPClient{Server: "smtp.example.com"}
    smtpClient.SendEmail("user@example.com", "Subject", "Body")
}

 

 

이 방법은 간단하지만 테스트 가능성을 방해하고 확장성을 제한하며 이메일 전송 전략 교체를 복잡하게 만듭니다.

 

DIP 수용: 추상화와 유연성 소개

DIP를 적용함으로써 구체적인 SMTP 구현이 아닌 추상적인 'EmailSender' 인터페이스에 의존하도록 애플리케이션을 리팩터링합니다. 추상화에 대한 의존성으로의 이러한 전환은 새로운 가능성의 영역을 열어줍니다.

 

이메일 전송 인터페이스 정의

package email

type EmailSender interface {
    SendEmail(to, subject, body string) error
}

 

SMTP 및 SendGrid용 인터페이스 구현

그런 다음 SMTP와 SendGrid 모두에 대한 EmailSender 인터페이스의 구체적인 구현을 만들어 일관된 인터페이스 뒤에 특정 이메일 전송 메커니즘을 캡슐화합니다.

// SMTP implementation
type SMTPSender struct { /* Do something... */ }

// SendGrid implementation
type SendGridSender struct { /* Do something... */ }

 

의존성 주입 활용

'EmailSender' 인터페이스를 사용하면 이제 고급 모듈에서 이메일 전달 방법의 세부 사항에 대해 걱정하지 않고도 알림을 보낼 수 있습니다.

package main

import (
    "myapp/email"
    "myapp/smtp"
)

func SendNotification(sender email.EmailSender, to, subject, body string) {
    // Now, the high-level module depends on the abstraction, not the concrete implementation.
    err := sender.SendEmail(to, subject, body)
    if err != nil {
        panic(err)
    }
}

func main() {
    sender := &smtp.SMTPSender{Server: "smtp.example.com"}
    SendNotification(sender, "user@example.com", "Subject", "Body")
}

 

SendGrid로의 원활한 전환

DIP 덕분에 SMTP에서 SendGrid로 전환하는 것은 SMTPSender 대신 SendGridSender 인스턴스를 삽입하는 것만큼 간단합니다. 높은 수준의 비즈니스 로직은 그대로 유지되어 교체 가능성과 유지 관리 가능성을 향상시키는 DIP의 힘을 보여줍니다.

package main

import (
    "myapp/email"
    "myapp/smtp"
)

func SendNotification(sender email.EmailSender, to, subject, body string) {
    // Now, the high-level module depends on the abstraction, not the concrete implementation.
    err := sender.SendEmail(to, subject, body)
    if err != nil {
        panic(err)
    }
}

func main() {
    sender = &sendgrid.SendGridSender{APIKey: "your_sendgrid_api_key_here"}
    SendNotification(sender, "user@example.com", "Subject", "Body")
}

 

DIP의 장점: 테스트 가능성, 확장성, 교체 가능성

이메일 전송 애플리케이션에 DIP를 적용하면 다음과 같은 몇 가지 주요 이점이 드러납니다.

  • 테스트 가능성: 테스트 중에 EmailSender 인터페이스를 모의하는 것은 간단하므로 실제 이메일을 전달할 필요가 없습니다.
  • 확장성: 새로운 이메일 서비스를 통합하는 것은 기존의 상위 수준 모듈을 변경하지 않고 EmailSender 인터페이스를 구현하는 문제입니다.
  • 교체 가능성: 종속성 주입을 통해 런타임에 이메일 전달 방법을 변경할 수 있으므로 시스템 적응성이 크게 향상됩니다.

 

결론: 소프트웨어 우수성을 위한 촉매제로서의 DIP

긴밀하게 결합된 SMTP 클라이언트에서 유연하고 추상화된 이메일 전송 아키텍처로의 여정은 현대 소프트웨어 개발에서 DIP의 중요한 역할을 보여줍니다. 추상화 및 종속성 주입의 우선순위를 지정함으로써 개발자는 테스트, 확장 및 유지 관리가 더 쉬운 소프트웨어를 만들어 더욱 강력하고 적응 가능한 애플리케이션을 위한 기반을 마련할 수 있습니다.

DIP 수용하는 것은 단순히 디자인 원칙을 고수하는 것이 아닙니다. 유연성, 확장성, 유지 관리 가능성을 중시하는 개발 문화를 조성하는 것입니다. Go 애플리케이션에서 살펴본 것처럼 약간의 반전이 이러한 목표를 달성하는 도움이 됩니다.

Comments