Heim  >  Artikel  >  Backend-Entwicklung  >  Warum es in Golang Probleme mit sauberer Architektur gibt und was besser funktioniert

Warum es in Golang Probleme mit sauberer Architektur gibt und was besser funktioniert

Mary-Kate Olsen
Mary-Kate OlsenOriginal
2024-11-07 01:59:02574Durchsuche

Why Clean Architecture Struggles in Golang and What Works Better

Golang hat sich einen guten Ruf als schnelle, effiziente Sprache erarbeitet, die Einfachheit in den Vordergrund stellt. Dies ist einer der Gründe, warum sie so häufig für Backend-Dienste, Microservices und Infrastrukturtools verwendet wird. Da jedoch immer mehr Entwickler von Sprachen wie Java und C# auf Go umsteigen, stellen sich Fragen zur Implementierung von Clean Architecture. Für diejenigen, die den schichtenbasierten Ansatz von Clean Architecture zur Strukturierung von Anwendungen gewohnt sind, kann es intuitiv sein, die gleichen Prinzipien auf Go anzuwenden. Wie wir jedoch noch sehen werden, geht der Versuch, Clean Architecture in Go zu implementieren, oft nach hinten los. Stattdessen werden wir uns eine Struktur ansehen, die auf die Stärken von Go zugeschnitten ist, einfacher und flexibler ist und mit der „Keep it simple“-Philosophie von Go übereinstimmt.

Warum sich saubere Architektur in Go fehl am Platz anfühlt

Das Ziel von Clean Architecture, das von Uncle Bob (Robert C. Martin) vertreten wird, besteht darin, Software zu entwickeln, die modular, testbar und einfach zu erweitern ist. Dies wird durch die Durchsetzung der Trennung von Belangen zwischen den Ebenen erreicht, wobei die Kerngeschäftslogik von externen Belangen isoliert bleibt. Während dies in stark objektorientierten Sprachen wie Java gut funktioniert, führt es in Go zu Reibungsverlusten. Hier ist der Grund:

1. Gos Minimalismus kämpft gegen übermäßige Abstraktionen

In Go wird großer Wert auf Lesbarkeit, Einfachheit und reduzierten Overhead gelegt. Clean Architecture führt Schichten über Schichten von Abstraktionen ein: Schnittstellen, Abhängigkeitsumkehr, komplexe Abhängigkeitsinjektion und Serviceschichten für Geschäftslogik. Allerdings führen diese zusätzlichen Ebenen bei der Implementierung in Go tendenziell zu unnötiger Komplexität.

Nehmen wir Kubernetes als Beispiel. Kubernetes ist ein riesiges Projekt, das in Go erstellt wurde, aber nicht auf den Prinzipien der Clean Architecture basiert. Stattdessen umfasst es eine flache, funktionsorientierte Struktur, die sich auf Pakete und Subsysteme konzentriert. Sie können dies im Kubernetes GitHub-Repository sehen, wo Pakete nach Funktionalität und nicht nach starren Ebenen organisiert sind. Durch die Gruppierung von Code basierend auf der Funktionalität erreicht Kubernetes eine hohe Modularität ohne komplexe Abstraktionen.

Die Go-Philosophie legt Wert auf Praktikabilität und Geschwindigkeit. Die Entwickler der Sprache haben sich stets dafür eingesetzt, eine übermäßige Architektur zu vermeiden und einfache Implementierungen zu bevorzugen. Wenn eine Abstraktion nicht unbedingt notwendig ist, gehört sie nicht in den Go-Code. Die Entwickler von Go haben die Sprache sogar ohne Vererbung entworfen, um die Fallstricke einer Überentwicklung zu vermeiden, und ermutigen Entwickler, ihre Designs sauber und klar zu halten.

2. Die Abhängigkeitsinjektion ist konstruktionsbedingt begrenzt

Clean Architecture stützt sich stark auf Dependency Injection, um verschiedene Schichten zu entkoppeln und Module testbarer zu machen. In Sprachen wie Java ist DI dank Frameworks wie Spring ein natürlicher Teil des Ökosystems. Diese Frameworks verarbeiten DI automatisch, sodass Sie Abhängigkeiten problemlos miteinander verbinden können, ohne Ihren Code zu überladen.

Allerdings fehlt Go ein natives DI-System und die meisten DI-Bibliotheken für Go sind entweder übermäßig komplex oder wirken unidiomatisch. Go verlässt sich auf die explizite Abhängigkeitsinjektion über Konstruktorfunktionen oder Funktionsparameter, um Abhängigkeiten klar zu halten und „Magie“ zu vermeiden, die in DI-Containern verborgen ist. Der Ansatz von Go macht den Code expliziter, bedeutet aber auch, dass das Abhängigkeitsmanagement unüberschaubar und ausführlich wird, wenn Sie zu viele Ebenen einführen.

In Kubernetes gibt es beispielsweise keine komplexen DI-Frameworks oder DI-Container. Stattdessen werden Abhängigkeiten mithilfe von Konstruktoren auf einfache Weise eingefügt. Dieses Design hält den Code transparent und vermeidet die Fallstricke von DI-Frameworks. Golang empfiehlt den Einsatz von DI nur dort, wo es wirklich Sinn macht, weshalb Kubernetes die Erstellung unnötiger Schnittstellen und Abhängigkeiten vermeidet, nur um einem Muster zu folgen.

3. Tests werden komplexer, wenn zu viele Ebenen vorhanden sind

Eine weitere Herausforderung bei Clean Architecture in Go besteht darin, dass das Testen unnötig kompliziert werden kann. In Java beispielsweise unterstützt Clean Architecture robuste Unit-Tests mit starkem Einsatz von Mocks für Abhängigkeiten. Mit Mocking können Sie jede Ebene isolieren und unabhängig testen. In Go kann das Erstellen von Mocks jedoch umständlich sein, und die Go-Community bevorzugt im Allgemeinen Integrationstests oder Tests mit echten Implementierungen, wann immer dies möglich ist.

In produktionstauglichen Go-Projekten wie Kubernetes erfolgt das Testen nicht durch die Isolierung jeder Komponente, sondern durch die Konzentration auf Integration und End-to-End-Tests, die reale Szenarien abdecken. Durch die Reduzierung der Abstraktionsebenen erreichen Go-Projekte wie Kubernetes eine hohe Testabdeckung und halten die Tests gleichzeitig nah am tatsächlichen Verhalten, was zu mehr Sicherheit bei der Bereitstellung in der Produktion führt.

Der beste architektonische Ansatz für Golang

Was also, wenn Clean Architecture nicht gut zu Go passt? Die Antwort liegt in einer einfacheren, funktionaleren Struktur, die den Schwerpunkt auf Pakete legt und sich auf Modularität statt strenger Schichtung konzentriert. Ein effektives Architekturmuster für Go basiert auf der Hexagonal Architecture, oft bekannt als Ports und Adapter. Diese Architektur ermöglicht Modularität und Flexibilität ohne übermäßige Schichtung.

Das Golang Standards Project Layout ist ein hervorragender Ausgangspunkt für die Erstellung produktionsreifer Projekte in Go. Diese Struktur bietet eine Grundlage für die Organisation von Code nach Zweck und Funktionalität und nicht nach Architekturebene.

Go-Projektstruktur: Ein praktisches Beispiel

Du hast vollkommen recht! Die Strukturierung von Go-Projekten mit einem paketorientierten Ansatz, bei dem die Funktionalität nach Paketen und nicht nach einer mehrschichtigen Ordnerstruktur aufgeschlüsselt wird, passt besser zu den Designprinzipien von Go. Anstatt Verzeichnisse der obersten Ebene nach Ebenen zu erstellen (z. B. Controller, Dienste, Repositorys), ist es in Go idiomatischer, zusammenhängende Pakete zu erstellen, die jeweils ihre eigenen Modelle, Dienste und Repositorys kapseln. Dieser paketbasierte Ansatz reduziert die Kopplung und hält den Code modular, was für eine Go-Anwendung in Produktionsqualität unerlässlich ist.

Sehen wir uns eine verfeinerte, paketzentrierte Struktur an, die für Go geeignet ist:

/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

Schlüsselkomponenten in der paketbasierten Struktur

  1. /cmd

Dieser Ordner ist der herkömmliche Speicherort für die Einstiegspunkte der Anwendung. Jeder Unterordner stellt hier eine andere ausführbare Datei für die App dar. Beispielsweise kann in Microservice-Architekturen jeder Dienst hier mit seiner main.go ein eigenes Verzeichnis haben. Der Code hier sollte minimal sein und nur für das Bootstrapping und das Einrichten von Abhängigkeiten verantwortlich sein.

  1. /config

Speichert Konfigurationsdateien und Setup-Logik, z. B. das Laden von Umgebungsvariablen oder externe Konfigurationen. Dieses Paket kann auch Strukturen für die Anwendungskonfiguration definieren.

  1. /intern

Hier befindet sich die Kernlogik der Anwendung, aufgeteilt in Pakete basierend auf der Funktionalität. Go schränkt den Zugriff auf interne Pakete von externen Modulen ein und hält diese Pakete für die Anwendung privat. Jedes Paket (z. B. Benutzer, Bestellung) ist in sich geschlossen und verfügt über eigene Modelle, Dienste und Repositorys. Dies ist der Schlüssel zur Go-Philosophie der Kapselung ohne übermäßige Schichtung.

  • /internal/user – Verwaltet alle benutzerbezogenen Funktionen, einschließlich Modelle (Datenstrukturen), Dienst (Geschäftslogik) und Repository (Datenbankinteraktion). Dadurch bleibt die benutzerbezogene Logik in einem Paket, was die Wartung erleichtert.

  • /internal/order – In ähnlicher Weise kapselt dieses Paket auftragsbezogenen Code. Jeder Funktionsbereich verfügt über eigene Modelle, Dienste und Repositorys.

  1. /Pkg

pkg enthält wiederverwendbare Komponenten, die in der gesamten Anwendung verwendet werden, aber nicht spezifisch für ein bestimmtes Paket sind. Hier werden Bibliotheken oder Dienstprogramme gespeichert, die unabhängig verwendet werden könnten, z. B. Auth zur Authentifizierung oder Logger zur benutzerdefinierten Protokollierung. Wenn diese Pakete besonders nützlich sind, können sie später auch in eigene Module extrahiert werden.

  1. /api

Das API-Paket dient als Schicht für HTTP- oder gRPC-Handler. Hier verarbeiten Handler eingehende Anfragen, rufen Dienste auf und geben Antworten zurück. Das Gruppieren von Handlern nach API-Version (z. B. v1) ist eine gute Praxis für die Versionierung und hilft dabei, zukünftige Änderungen isoliert zu halten.

  1. /utils

Allgemeine Dienstprogramme, die nicht an ein bestimmtes Paket gebunden sind, sondern einen übergreifenden Zweck in der gesamten Codebasis erfüllen (z. B. Datumsanalyse, Zeichenfolgenmanipulation). Es ist hilfreich, dies minimal zu halten und sich auf reine Hilfsfunktionen zu konzentrieren.

Beispielcode-Layout für das Benutzerpaket

Um die Struktur zu veranschaulichen, sehen Sie sich hier genauer an, wie das Benutzerpaket aussehen könnte:

models.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

service.go

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

package user

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

Repository.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)
}

Warum diese paketbasierte Struktur ideal für Go ist

Diese Struktur passt gut zu Gos Redewendungen:

  1. Kapselung

Durch die Organisation von Paketen nach Funktionalität ist der Code natürlich gekapselt und modular. Jedes Paket besitzt seine eigenen Modelle, Dienste und Repositorys, wodurch der Code kohärent und hochgradig modular bleibt. Dies erleichtert das Navigieren, Verstehen und Testen einzelner Pakete.

  1. Minimale Schnittstellen

Schnittstellen werden nur an den Paketgrenzen (z. B. UserRepository) verwendet, wo sie für Tests und Flexibilität am sinnvollsten sind. Dieser Ansatz reduziert die Unordnung unnötiger Schnittstellen, die die Wartung von Go-Code erschweren können.

  1. Explizite Abhängigkeitsinjektion

Abhängigkeiten werden über Konstruktorfunktionen (z. B. NewUserService) eingefügt. Dadurch bleiben Abhängigkeiten explizit und es sind keine komplexen Dependency-Injection-Frameworks erforderlich, wodurch Gos auf Einfachheit ausgerichtetes Design treu bleibt.

  1. Wiederverwendbarkeit in /pkg

Komponenten wie Auth und Logger im pkg-Verzeichnis können paketübergreifend gemeinsam genutzt werden, wodurch die Wiederverwendbarkeit ohne übermäßige Kopplung gefördert wird.

  1. Klare API-Struktur

Durch die Gruppierung von Handlern unter /api ist es einfach, die API-Ebene zu skalieren und neue Versionen oder Handler hinzuzufügen, wenn die Anwendung wächst. Jeder Handler kann sich auf die Bearbeitung von Anfragen und die Koordinierung mit Diensten konzentrieren und so den Code modular und sauber halten.

Mit dieser paketzentrierten Struktur können Sie skalieren, wenn Sie weitere Domänen (z. B. Produkt, Inventar) hinzufügen, jede mit ihren eigenen Modellen, Diensten und Repositorys. Die Trennung nach Domänen steht im Einklang mit Gos idiomatischer Art, Code zu organisieren, wobei Einfachheit und Klarheit gegenüber starrer Schichtung treu bleiben.

Meinungen und reale Erfahrungen

Nach meiner Erfahrung mit Go verkompliziert Clean Architecture oft die Codebasis, ohne einen nennenswerten Mehrwert zu schaffen. Eine saubere Architektur ist in der Regel sinnvoll, wenn große, unternehmenstaugliche Anwendungen in Sprachen wie Java erstellt werden, wo viel integrierte Unterstützung für DI vorhanden ist und die Verwaltung tiefer Vererbungsstrukturen ein allgemeiner Bedarf ist. Der Minimalismus von Go, seine Einfachheit-zuerst-Denkweise und sein unkomplizierter Ansatz zur Parallelität und Fehlerbehandlung schaffen jedoch ein völlig anderes Ökosystem.

Fazit: Machen Sie sich die idiomatische Architektur von Go zu eigen

Wenn Sie einen Java-Hintergrund haben, könnte es verlockend sein, Clean Architecture to Go anzuwenden. Die Stärken von Go liegen jedoch in der Einfachheit, Transparenz und Modularität ohne starke Abstraktion. Eine ideale Architektur für Go priorisiert Pakete, die nach Funktionalität, minimalen Schnittstellen, expliziter DI, realistischen Tests und Adaptern für Flexibilität organisiert sind.

Achten Sie beim Entwerfen eines Go-Projekts auf reale Beispiele wie Kubernetes, Vault und das Golang Standards Project Layout. Diese zeigen, wie leistungsstark Go sein kann, wenn die Architektur Einfachheit statt starrer Struktur bevorzugt. Anstatt zu versuchen, Go in eine Clean-Architecture-Form zu bringen, setzen Sie auf eine Architektur, die genauso unkompliziert und effizient ist wie Go selbst. Auf diese Weise erstellen Sie eine Codebasis, die nicht nur idiomatisch ist, sondern auch einfacher zu verstehen, zu warten und zu skalieren ist.

Das obige ist der detaillierte Inhalt vonWarum es in Golang Probleme mit sauberer Architektur gibt und was besser funktioniert. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn