So strukturieren Sie eine Go-Anwendung, die gemäß der übersichtlichen Architektur aufgebaut ist

9

Ich versuche, ein Projekt mit der hier beschriebenen sauberen Architektur zu erstellen . Ich habe in Go einen großartigen Artikel darüber gefunden .

Das Beispiel ist sehr einfach, und der Autor fügt seinen Code in Pakete ein, deren Namen auf der Ebene basieren, in der sie sich befinden. Ich mag die Idee von Onkel Bob, dass die Architektur einer Anwendung ihre Absicht klar kommunizieren sollte . Daher möchte ich, dass meine Anwendung Pakete der obersten Ebene enthält, die auf Domänenbereichen basieren. Meine Dateistruktur würde also ungefähr so ​​aussehen:

/Customers
    /domain.go
    /interactor.go
    /interface.go
    /repository.go
/... the same for other domain areas

Das Problem dabei ist, dass mehrere Ebenen dasselbe Paket verwenden. Es ist also nicht ganz klar, wann die Abhängigkeitsregel verletzt wird, da Sie keine Importe haben, die zeigen, was von was abhängt.

Ich komme aus einem Python-Hintergrund, in dem dies kein so großes Problem darstellt, da Sie einzelne Dateien importieren können, also customers.interactorimportieren können customers.domain.

Wir könnten in gO etwas Ähnliches erreichen, indem wir Pakete verschachteln, sodass das Kundenpaket ein Domänenpaket und ein Interaktorpaket usw. enthält. Dies fühlt sich klobig an, und gleichnamige Pakete können ärgerlich sein.

Eine andere Möglichkeit wäre, mehrere Pakete pro Domain-Bereich zu erstellen. Eine namens customer_domain, eine namens customer_interactor usw. Aber das fühlt sich auch schmutzig an. Es passt nicht gut zu den Richtlinien für die Paketbenennung von Go und sieht so aus, als ob alle diese separaten Pakete irgendwie gruppiert werden sollten, da ihre Namen ein gemeinsames Präfix haben.

Was wäre ein gutes Dateilayout dafür?

Bigblind
quelle
Als jemand, der unter einer Architektur gelitten hat, die Pakete ausschließlich nach Architekturebene organisiert hat, möchte ich meine Unterstützung für funktionsbasierte Pakete zum Ausdruck bringen . Ich bin alles für die Kommunikation von Absichten, aber lass mich nicht durch jede dunkle Ecke kriechen, nur um eine neue Funktion hinzuzufügen.
candied_orange
Mit Absicht meine ich, dass Pakete kommunizieren sollten, worum es in der Anwendung geht. Wenn Sie also eine Bibliotheksanwendung erstellen, haben Sie ein
Buchpaket

Antworten:

5

Hierfür gibt es einige Lösungen:

  1. Pakettrennung
  2. Analyse überprüfen
  3. Statische Analyse
  4. Laufzeitanalyse

Jeder hat seine Vor- und Nachteile.

Pakettrennung

Dies ist der einfachste Weg, bei dem nichts extra gebaut werden muss. Es gibt zwei Geschmacksrichtungen:

// /app/user/model/model.go
package usermodel
type User struct {}

// /app/user/controller/controller.go
package usercontroller
import "app/user/model"
type Controller struct {}

Oder:

// /app/model/user.go
package model
type User struct {}

// /app/controller/user.go
package controller
import "app/user/model"

type User struct {}

Dies bricht jedoch die Ganzheit des Konzepts von User. Um zu verstehen oder zu ändern User, müssen Sie mehrere Pakete berühren.

Es hat jedoch eine gute Eigenschaft, die beim modelImportieren offensichtlicher ist controller, und in gewissem Umfang wird es durch die Sprachsemantik erzwungen.

Analyse überprüfen

Wenn die Anwendung nicht groß ist (weniger als 30KLOC) und Sie gute Programmierer haben, ist es normalerweise nicht erforderlich, etwas zu erstellen. Wertorientierte Strukturen zu organisieren ist ausreichend, zB:

// /app/user/user.go
package user
type User struct {}
type Controller struct {}

Oft sind die "Constraint-Verstöße" von geringer Bedeutung oder leicht zu beheben. Es schadet der Klarheit und Verständlichkeit - solange Sie es nicht außer Kontrolle geraten lassen, müssen Sie sich darüber keine Sorgen machen.

Statische / Laufzeitanalyse

Sie können diese Fehler auch mithilfe der statischen Analyse oder der Laufzeitanalyse mithilfe von Anmerkungen ermitteln:

Statisch:

// /app/user/user.go
package user

// architecture: model
type User struct {}

// architecture: controller
type Controller struct {}

Dynamisch:

// /app/user/user.go
package user

import "app/constraint"

var _ = constraint.Model(&User{})
type User struct {}

var _ = constraint.Controller(&Controller{})
type Controller struct {}

// /app/main.go
package main

import "app/constraint"

func init() { constraint.Check() }

Sowohl statisch als auch dynamisch kann auch über Felder erfolgen:

// /app/user/user.go
package user

import "app/constraint"

type User struct {   
    _ constraint.Model
}

type Controller struct {
    _ constraint.Controller
}

Natürlich wird die Suche nach solchen Dingen komplizierter.

Andere Versionen

Solche Ansätze können an anderer Stelle verwendet werden, nicht nur bei Typeinschränkungen, sondern auch bei Funktionsnamen, APIs usw.

https://play.golang.org/p/4bCOV3tYz7

Egon
quelle