Ich habe in meinem Vorgängerbeitrag bereits von "Drinnen" und "Draußen"-Architekturen gesprochen. Anknüpfend an diese Sichtweise schlage ich die folgende Paketstruktur für Hexagonale Architekturen vor:
projectroot
application
domain
usecases
ports
in
out
context
sql
http
messaging
Die Struktur spiegelt die drei zentralen Hexagone der Anwendung wieder. Im Kern findet sich das "application"-Hexagon. Dieses enthält das Core-Domain-Modell, sowie die Usecases der Anwendung. Das "ports"-Hexagon enthält die Interfaces für ein- und ausgehende Verbindungen. Interfaces für eingehende Verbindungen liegen im "ports/in"-Package und werden von Usecase-Typen implementiert. Beispiel: Das Interface BicyleConfigurations
package in
type BicyleConfigurations interface {
GetAll() ([]domain.Configuration, error)
}
wird vom Usecase GetBicycleConfigurations implementiert:
package usecases
type GetBicycleConfigurations struct{
}
func (g GetBicycleConfigurations) GetAll() ([]domain.Configuration, error) {
...
}
Interfaces für die von der Anwendung genutzten Services liegen im Package "ports/out" und werden von Typen im "context"-Hexagon implementiert. Beispiel: Das Interface BikeRepository
package out
type BikeRepository interface {
GetAll() ([]domain.Configuration, error)
}
wird vom Adapter PostgresAdapter implementiert, der im context-Hexagon liegt:
package context
type PostgresAdapter struct {
dbpool *pgxpool.Pool
}
func (a PostgresAdapter) GetAll() ([]domain.Configuration, error) {
rows, _ := a.dbpool.Query(context.Background(), "select * from configurations")
defer rows.Close()
var configurations []domain.Configuration
for rows.Next() {
// ...
}
return configurations, nil
}
Die vorgeschlagene Package-Struktur hat den Vorteil, dass die drei Toplevel-Hexagone unmittelbar erkennen lassen, um welchen Teil der Anwendung es sich jeweils handelt. Neue Entwickler:innen finden sich schnell zurecht, Diskussionen darüber, wo etwas implementiert wird, werden von vornherein vermieden. Der Usecase-zentrierte Ansatz forciert außerdem eine Domänen-orientierte Sichtweise, so dass sich Anwendungsfälle direkt im Code gespiegelt finden.