>  기사  >  백엔드 개발  >  Golang에서 클린 아키텍처가 어려움을 겪는 이유와 더 나은 기능은 무엇입니까?

Golang에서 클린 아키텍처가 어려움을 겪는 이유와 더 나은 기능은 무엇입니까?

Mary-Kate Olsen
Mary-Kate Olsen원래의
2024-11-07 01:59:02493검색

Why Clean Architecture Struggles in Golang and What Works Better

Golang은 단순성을 우선시하는 빠르고 효율적인 언어로 확고한 명성을 얻었습니다. 이는 Golang이 백엔드 서비스, 마이크로서비스 및 인프라 도구에 널리 사용되는 이유 중 하나입니다. 그러나 Java 및 C#과 같은 언어에서 Go로 전환하는 개발자가 늘어나면서 Clean Architecture 구현에 대한 질문이 제기됩니다. 애플리케이션 구조화에 대한 Clean Architecture의 레이어 기반 접근 방식에 익숙한 사람들에게는 동일한 원칙을 Go에 적용하는 것이 직관적으로 느껴질 수 있습니다. 그러나 앞으로 살펴보겠지만 Go에서 클린 아키텍처를 구현하려는 시도는 종종 역효과를 낳습니다. 대신 Go의 장점에 맞춰 더 간단하고 유연하며 Go의 '단순함 유지' 철학에 부합하는 구조를 살펴보겠습니다.

Go에서 클린 아키텍처가 어울리지 않는 이유

밥 삼촌(로버트 C. 마틴)이 옹호하는 클린 아키텍처의 목표는 모듈식이고 테스트 가능하며 확장하기 쉬운 소프트웨어를 만드는 것입니다. 이는 핵심 비즈니스 로직을 외부 문제와 격리하여 계층 간 문제를 분리함으로써 달성됩니다. 이는 Java와 같은 고도로 객체 지향적인 언어에서는 잘 작동하지만 Go에서는 마찰이 발생합니다. 이유는 다음과 같습니다.

1. Go의 미니멀리즘은 과도한 추상화에 맞서 싸웁니다.

Go에서는 가독성, 단순성 및 오버헤드 감소에 중점을 둡니다. 클린 아키텍처는 인터페이스, 종속성 반전, 복잡한 종속성 주입, 비즈니스 로직을 위한 서비스 계층 등 추상화 계층 위에 계층을 도입합니다. 그러나 이러한 추가 레이어는 Go에서 구현될 때 불필요한 복잡성을 추가하는 경향이 있습니다.

쿠버네티스를 예로 들어보겠습니다. Kubernetes는 Go로 구축된 대규모 프로젝트이지만 Clean Architecture 원칙에 의존하지 않습니다. 대신 패키지와 하위 시스템에 초점을 맞춘 평면적이고 기능 지향적인 구조를 수용합니다. 패키지가 엄격한 레이어가 아닌 기능별로 구성되어 있는 Kubernetes GitHub 저장소에서 이를 확인할 수 있습니다. Kubernetes는 기능에 따라 코드를 그룹화함으로써 복잡한 추상화 없이 높은 모듈성을 달성합니다.

Go 철학은 실용성과 속도를 우선시합니다. 언어 제작자는 과도한 아키텍처를 피하고 간단한 구현을 선호한다고 지속적으로 옹호해 왔습니다. 추상화가 꼭 필요하지 않다면 Go 코드에 속하지 않습니다. Go의 제작자는 과도한 엔지니어링의 함정을 피하기 위해 상속 없이 언어를 설계하여 개발자가 디자인을 깨끗하고 명확하게 유지하도록 장려했습니다.

2. 의존성 주입은 설계상 제한됩니다.

클린 아키텍처는 다양한 레이어를 분리하고 모듈을 더 쉽게 테스트할 수 있도록 종속성 주입에 크게 의존합니다. Java와 같은 언어에서 DI는 Spring과 같은 프레임워크 덕분에 생태계의 자연스러운 일부입니다. 이러한 프레임워크는 DI를 자동으로 처리하므로 코드를 복잡하게 만들지 않고도 종속성을 쉽게 연결할 수 있습니다.

그러나 Go에는 기본 DI 시스템이 부족하며 대부분의 Go용 DI 라이브러리는 지나치게 복잡하거나 단조로운 느낌을 줍니다. Go는 생성자 함수 또는 함수 매개변수를 통한 명시적 종속성 주입을 사용하여 종속성을 명확하게 유지하고 DI 컨테이너에 숨겨진 "마법"을 방지합니다. Go의 접근 방식은 코드를 더욱 명시적으로 만들지만 너무 많은 레이어를 도입하면 종속성 관리가 관리하기 어렵고 장황해집니다.

예를 들어 Kubernetes에서는 복잡한 DI 프레임워크나 DI 컨테이너가 표시되지 않습니다. 대신 생성자를 사용하여 간단한 방식으로 종속성을 주입합니다. 이 디자인은 코드를 투명하게 유지하고 DI 프레임워크의 함정을 방지합니다. Golang은 정말로 의미가 있는 경우에만 DI 사용을 권장합니다. 이것이 바로 Kubernetes가 단지 패턴을 따르기 위해 불필요한 인터페이스와 종속성을 생성하는 것을 방지하는 이유입니다.

3. 레이어가 너무 많으면 테스트가 더욱 복잡해집니다.

Go에서 Clean Architecture의 또 다른 과제는 테스트를 불필요하게 복잡하게 만들 수 있다는 것입니다. 예를 들어 Java에서 Clean Architecture는 종속성에 대한 모의 개체를 많이 사용하여 강력한 단위 테스트를 지원합니다. Mocking을 사용하면 각 레이어를 분리하여 독립적으로 테스트할 수 있습니다. 그러나 Go에서는 모의 객체를 만드는 것이 번거로울 수 있으며 Go 커뮤니티는 일반적으로 가능한 경우 통합 테스트 또는 실제 구현을 사용한 테스트를 선호합니다.

Kubernetes와 같은 프로덕션급 Go 프로젝트에서는 테스트가 각 구성 요소를 분리하여 처리되는 것이 아니라 실제 시나리오를 포괄하는 통합 및 엔드투엔드 테스트에 중점을 두어 처리됩니다. 추상화 계층을 줄임으로써 Kubernetes와 같은 Go 프로젝트는 테스트를 실제 동작에 가깝게 유지하면서 높은 테스트 적용 범위를 달성하므로 프로덕션 환경에 배포할 때 더 큰 자신감을 얻을 수 있습니다.

Golang을 위한 최고의 아키텍처 접근 방식

클린 아키텍처가 Go와 잘 맞지 않는다면 어떻게 될까요? 그 대답은 패키지를 강조하고 엄격한 계층화보다 모듈성에 초점을 맞춘 더 단순하고 기능적인 구조에 있습니다. Go의 효과적인 아키텍처 패턴 중 하나는 포트 및 어댑터라고도 알려진 육각형 아키텍처를 기반으로 하는 것입니다. 이 아키텍처는 과도한 계층화 없이 모듈성과 유연성을 허용합니다.

Golang 표준 프로젝트 레이아웃은 Go에서 즉시 제작 가능한 프로젝트를 만들기 위한 훌륭한 출발점입니다. 이 구조는 아키텍처 계층이 아닌 목적과 기능별로 코드를 구성하기 위한 기반을 제공합니다.

Go 프로젝트 구조: 실제 예

당신 말이 정말 맞아요! 계층화된 폴더 구조가 아닌 패키지별로 기능을 분류하는 패키지 중심 접근 방식으로 Go 프로젝트를 구조화하는 것은 Go의 디자인 원칙에 더 잘 맞습니다. 레이어(예: 컨트롤러, 서비스, 저장소)별로 최상위 디렉토리를 만드는 대신 Go에서는 각각 자체 모델, 서비스 및 저장소를 캡슐화하는 응집력 있는 패키지를 만드는 것이 더 관용적입니다. 이 패키지 기반 접근 방식은 결합을 줄이고 프로덕션급 Go 애플리케이션에 필수적인 코드 모듈식을 유지합니다.

Go에 적합한 세련된 패키지 중심 구조를 살펴보겠습니다.

/myapp
   /cmd                   // Entrypoints for different executables (e.g., main.go)
      /myapp-api
         main.go          // Entrypoint for the main application
   /config                // Configuration files and setup
   /internal              // Private/internal packages (not accessible externally)
      /user               // Package focused on user-related functionality
         models.go        // Data models and structs specific to user functionality
         service.go       // Core business logic for user operations
         repository.go    // Database access methods for user data
      /order              // Package for order-related logic
         models.go        // Data models for orders
         service.go       // Core order-related logic
         repository.go    // Database access for orders
   /pkg                   // Shared, reusable packages across the application
      /auth               // Authorization and authentication package
      /logger             // Custom logging utilities
   /api                   // Package with REST or gRPC handlers
      /v1
         user_handler.go  // Handler for user-related endpoints
         order_handler.go // Handler for order-related endpoints
   /utils                 // General-purpose utility functions and helpers
   go.mod                 // Module file

패키지 기반 구조의 주요 구성 요소

  1. /cmd

이 폴더는 애플리케이션 진입점의 일반적인 위치입니다. 여기의 각 하위 폴더는 앱의 서로 다른 실행 파일을 나타냅니다. 예를 들어, 마이크로서비스 아키텍처에서 각 서비스는 여기에서 main.go를 포함하는 자체 디렉터리를 가질 수 있습니다. 여기의 코드는 최소화되어야 하며 부트스트래핑 및 종속성 설정만 담당해야 합니다.

  1. /구성

환경 변수 로드 또는 외부 구성과 같은 구성 파일 및 설정 로직을 저장합니다. 이 패키지는 애플리케이션 구성을 위한 구조를 정의할 수도 있습니다.

  1. /내부

여기에는 애플리케이션의 핵심 로직이 위치하며 기능에 따라 패키지로 분할됩니다. Go는 외부 모듈에서 내부 패키지에 대한 액세스를 제한하여 이러한 패키지를 애플리케이션에 비공개로 유지합니다. 각 패키지(예: 사용자, 주문)는 자체 모델, 서비스 및 저장소를 포함하여 독립적입니다. 이것이 과도한 레이어링 없이 캡슐화한다는 Go의 철학의 핵심입니다.

  • /internal/user – 모델(데이터 구조), 서비스(비즈니스 로직) 및 저장소(데이터베이스 상호 작용)를 포함하여 모든 사용자 관련 기능을 관리합니다. 이는 사용자 관련 로직을 하나의 패키지에 담아 유지 관리가 용이합니다.

  • /internal/order – 마찬가지로 이 패키지에는 주문 관련 코드가 캡슐화되어 있습니다. 각 기능 영역에는 고유한 모델, 서비스 및 저장소가 있습니다.

  1. /패키지

pkg에는 애플리케이션 전체에서 사용되지만 특정 패키지에만 국한되지 않는 재사용 가능한 구성 요소가 포함되어 있습니다. 인증을 위한 auth 또는 사용자 정의 로깅을 위한 로거와 같이 독립적으로 사용할 수 있는 라이브러리 또는 유틸리티가 여기에 보관됩니다. 이러한 패키지가 특히 유용하다면 나중에 자체 모듈로 추출할 수도 있습니다.

  1. /api

API 패키지는 HTTP 또는 gRPC 핸들러의 레이어 역할을 합니다. 여기서 핸들러는 들어오는 요청을 처리하고, 서비스를 호출하고, 응답을 반환합니다. API 버전(예: v1)별로 핸들러를 그룹화하는 것은 버전 관리를 위한 좋은 방법이며 향후 변경 사항을 격리하는 데 도움이 됩니다.

  1. /유틸리티

특정 패키지에 연결되지 않지만 코드베이스 전체에서 교차 목적(예: 날짜 구문 분석, 문자열 조작)을 제공하는 범용 유틸리티입니다. 이를 최소화하고 순전히 유틸리티 기능에만 집중하는 것이 도움이 됩니다.

사용자 패키지의 예제 코드 레이아웃

구조를 설명하기 위해 사용자 패키지의 모습을 자세히 살펴보겠습니다.

모델스닷컴

/myapp
   /cmd                   // Entrypoints for different executables (e.g., main.go)
      /myapp-api
         main.go          // Entrypoint for the main application
   /config                // Configuration files and setup
   /internal              // Private/internal packages (not accessible externally)
      /user               // Package focused on user-related functionality
         models.go        // Data models and structs specific to user functionality
         service.go       // Core business logic for user operations
         repository.go    // Database access methods for user data
      /order              // Package for order-related logic
         models.go        // Data models for orders
         service.go       // Core order-related logic
         repository.go    // Database access for orders
   /pkg                   // Shared, reusable packages across the application
      /auth               // Authorization and authentication package
      /logger             // Custom logging utilities
   /api                   // Package with REST or gRPC handlers
      /v1
         user_handler.go  // Handler for user-related endpoints
         order_handler.go // Handler for order-related endpoints
   /utils                 // General-purpose utility functions and helpers
   go.mod                 // Module file

service.go

// models.go - Defines the data structures related to users

package user

type User struct {
    ID       int
    Name     string
    Email    string
    Password string
}

저장소.go

// service.go - Contains the core business logic for user operations

package user

type UserService struct {
    repo UserRepository
}

// NewUserService creates a new instance of UserService
func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) RegisterUser(name, email, password string) error {
    // Business logic for registering a user
    newUser := User{Name: name, Email: email, Password: password}
    return s.repo.Save(newUser)
}

이 패키지 기반 구조가 Go에 이상적인 이유

이 구조는 Go의 관용어와 잘 어울립니다.

  1. 캡슐화

기능에 따라 패키지를 구성함으로써 코드가 자연스럽게 캡슐화되고 모듈화됩니다. 각 패키지는 해당 모델, 서비스 및 리포지토리를 소유하여 코드를 응집력 있고 고도로 모듈화되도록 유지합니다. 이를 통해 개별 패키지를 더 쉽게 탐색하고, 이해하고, 테스트할 수 있습니다.

  1. 최소 인터페이스

인터페이스는 테스트 및 유연성에 가장 적합한 패키지 경계(예: UserRepository)에서만 사용됩니다. 이 접근 방식을 사용하면 불필요한 인터페이스가 복잡해져서 Go 코드를 유지 관리하기가 더 어려워질 수 있습니다.

  1. 명시적 종속성 주입

종속성은 생성자 함수(예: NewUserService)를 통해 주입됩니다. 이는 종속성을 명시적으로 유지하고 복잡한 종속성 주입 프레임워크가 필요하지 않도록 하여 Go의 단순성에 초점을 맞춘 디자인을 그대로 유지합니다.

  1. /pkg의 재사용성

pkg 디렉토리에 있는 auth 및 logger와 같은 구성 요소는 패키지 전체에서 공유될 수 있으므로 과도한 결합 없이 재사용이 가능합니다.

  1. 명확한 API 구조

/api 아래에 핸들러를 그룹화하면 쉽게 API 계층을 확장하고 애플리케이션이 성장함에 따라 새 버전이나 핸들러를 추가할 수 있습니다. 각 핸들러는 요청 처리 및 서비스 조정에 집중하여 코드를 모듈식으로 깔끔하게 유지합니다.

이 패키지 중심 구조를 사용하면 도메인(예: 제품, 인벤토리)을 추가하면서 확장할 수 있으며 각 도메인에는 자체 모델, 서비스 및 저장소가 있습니다. 도메인별 분리는 Go의 관용적인 코드 구성 방식과 일치하며 엄격한 레이어링보다 단순성과 명확성을 유지합니다.

의견과 실제 경험

Go를 사용한 경험에 따르면 클린 아키텍처는 중요한 가치를 추가하지 않고 코드베이스를 복잡하게 만드는 경우가 많습니다. Clean Architecture는 DI에 대한 기본 지원이 많이 내장되어 있고 심층 상속 구조를 관리하는 것이 일반적인 요구 사항인 Java와 같은 언어로 대규모 엔터프라이즈급 애플리케이션을 구축할 때 적합한 경향이 있습니다. 그러나 Go의 미니멀리즘, 단순성 우선 사고방식, 동시성 및 오류 처리에 대한 직접적인 접근 방식은 전혀 다른 생태계를 만들어냅니다.

결론: Go의 관용적 아키텍처를 수용하세요

Java 배경을 갖고 있다면 Clean Architecture to Go를 적용하고 싶을 수도 있습니다. 그러나 Go의 강점은 단순성, 투명성, 과도한 추상화가 없는 모듈성에 있습니다. Go를 위한 이상적인 아키텍처는 기능, 최소한의 인터페이스, 명시적 DI, 현실적인 테스트 및 유연성을 위한 어댑터로 구성된 패키지의 우선순위를 지정합니다.

Go 프로젝트를 설계할 때 Kubernetes, Vault, Golang 표준 프로젝트 레이아웃과 같은 실제 사례를 살펴보세요. 이는 아키텍처가 견고한 구조보다 단순성을 수용할 때 Go가 얼마나 강력한지를 보여줍니다. Go를 클린 아키텍처 틀에 맞추려고 하기보다는 Go만큼 간단하고 효율적인 아키텍처를 수용하세요. 이렇게 하면 관용적일 뿐만 아니라 더 쉽게 이해하고 유지 관리하고 확장할 수 있는 코드베이스를 구축할 수 있습니다.

위 내용은 Golang에서 클린 아키텍처가 어려움을 겪는 이유와 더 나은 기능은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.