Datenmodell und Codegenerierung

Die Datei:

herd/_model.goat

beschreibt die Struktur der Anwendung und ist die wichtigste Informationsquelle, die vom Goat-Generator verwendet wird.

Das Modell definiert unter anderem:

  • Anwendungsmetadaten,
  • unterstützte Sprachen,
  • Benutzerrollen,
  • Entitäten,
  • Entitätseigenschaften,
  • Beziehungen,
  • Datenbeschränkungen,
  • Lese- und Schreibberechtigungen,
  • Entitäten zugewiesene Module,
  • CRUD-Verhalten,
  • SEO- und SSR-Elemente.

Auf Grundlage dieser Informationen kann Goat konsistente Elemente für viele Anwendungsschichten generieren, unter anderem:

  • Go-Modelle,
  • DAOs und Repositories,
  • DTOs,
  • APIs,
  • CRUD-Befehle,
  • SQL-Migrationen,
  • Formulare,
  • Listen und Verwaltungsansichten,
  • Frontend-Elemente.

Der wichtigste Vorteil besteht nicht allein in der Dateigenerierung. Das Modell ermöglicht es, eine bestimmte Struktur einmal zu beschreiben und diese Definition anschließend in vielen Teilen der Anwendung zu verwenden.

Statt Backend, Datenbank, API und Frontend manuell zu synchronisieren, kannst du das Modell ändern und den Generator die daraus resultierenden Anpassungen erstellen lassen.

Das Modell als Anwendungsbeschreibung

herd/_model.goat sollte vor allem die Absicht und Struktur der Anwendung beschreiben, nicht die Implementierungsdetails jeder generierten Datei.

Vereinfacht dargestellt:

_model.goat
    ↓
Domänenmodell
    ↓
Generator
    ↓
Backend + Datenbank + API + Frontend

Beispielsweise kann die Definition eines Feldes:

add --name=title --type=web_title

nicht nur das Backend-Modell beeinflussen, sondern auch die Art der Datenspeicherung, die generierten DTOs, Formulare und Ansichten.

Dadurch muss dieselbe Information nicht mehrfach in verschiedenen Technologien deklariert werden.

Anwendungsmetadaten

Das Modell beginnt mit der grundlegenden Konfiguration der Anwendung:

app --name goat \
    --version 0.0.1 \
    --path app \
    --go-prefix code.pozoga.eu/spozoga/goatcms.com \
    --terminal-prefix APP \
    --default-language=en \
    --supported-languages=pl,en,de

Die Definition legt unter anderem fest:

  • den Namen der Anwendung,
  • die Version,
  • den Projektpfad,
  • das Präfix der Go-Module,
  • das vom Terminal verwendete Präfix,
  • die Standardsprache,
  • die Liste der unterstützten Sprachen.

Diese Informationen können später vom Generator sowie von einzelnen Anwendungsmodulen verwendet werden.

Rollen und Berechtigungen

Rollen werden direkt im Modell definiert.

Beispiel:

role:add --name=admin --super
role:add --name=manager
role:add --name=user

In diesem Fall verfügt die Anwendung über drei Rollen:

  • admin,
  • manager,
  • user.

Das Flag:

--super

kennzeichnet eine Rolle mit erweiterten Systemberechtigungen.

Rollen können später bei der Definition des Zugriffs auf Entitätseigenschaften verwendet werden.

Beispiel:

def --write=admin,manager --read=user

ermöglicht es festzulegen, welche Rollen Daten ändern und welche sie lesen dürfen.

Dadurch können grundlegende Zugriffsregeln bereits auf Modellebene definiert werden, statt sie unabhängig in API, Formularen und Backend zu wiederholen.

Basisentitäten

Wenn mehrere Entitäten eine gemeinsame Struktur besitzen, empfiehlt es sich, diese in eine Basisentität auszulagern.

Ein Beispiel ist content:

entity:base:add --name content \
    --label slug \
    --base base_entity

Eine Basisentität kann gemeinsame Elemente definieren:

  • Eigenschaften,
  • Beziehungen,
  • Beschränkungen,
  • Zugriffsregeln.

Im Projekt content repräsentiert die grundlegende Struktur von in der Anwendung veröffentlichten Inhalten.

Sie kann anschließend verwendet werden von:

  • Seiten,
  • Dokumentation,
  • Artikeln,
  • anderen Inhaltstypen.

Entitätseigenschaften

Eigenschaften werden innerhalb des Abschnitts --properties definiert.

Beispiel:

--properties=<<PROPERTIESEOF
    def --write=admin,manager --read=user --list

    add --name=title \
        --type=web_title \
        --doc="Title is a short heading that describes the content."

    add --name=slug \
        --type=web_slug \
        --required \
        --doc="Slug is a URL-friendly name for the content."

    add --name=lang \
        --type=language \
        --required \
        --doc="Language of the current content."
PROPERTIESEOF

Jede Eigenschaft kann unter anderem festlegen:

  • den Namen,
  • den Typ,
  • die Pflichtangabe,
  • die Darstellungsweise,
  • den Zugriff,
  • die Dokumentation,
  • zusätzliches Generatorverhalten.

Der Eigenschaftstyp enthält mehr Informationen als allein der Spaltentyp in der Datenbank.

Beispielsweise:

web_title
web_slug
language
seo_description
block_content
file_set

können auch die Art der Validierung, Serialisierung und Darstellung von Daten in der Benutzeroberfläche bestimmen.

Vererbung von Entitäten

Entitäten können Basisentitäten erweitern.

Beispiel einer Dokumentationsentität:

entity:extended --name doc --base content

bedeutet, dass doc die für content definierten Eigenschaften und Verhaltensweisen erbt.

Dadurch lässt sich die Wiederholung von Feldern wie:

title
slug
lang
description
body

in jeder Entität vermeiden, die Inhalte repräsentiert.

Eine Entität kann gleichzeitig eigene Eigenschaften hinzufügen.

Beispiel:

--properties=<<PROPERTIESEOF
    def --write=admin,manager --read=user --list

    add --name=ver \
        --type=short_text \
        --doc="Goat CLI version number for the page"
PROPERTIESEOF

Auf diese Weise behält doc das gesamte Modell von content bei, speichert jedoch zusätzlich die Version der Dokumentation.

Beziehungen

Beziehungen zwischen Entitäten können direkt im Modell definiert werden.

Beispiel:

--relations=<<RELATIONSEOF
    def --onremove

    add --name=owner \
        --to=user \
        --doc="Owner is the author of the article."
RELATIONSEOF

Die Definition bestimmt eine owner-Beziehung zur Entität user.

Der Generator kann solche Informationen an vielen Stellen verwenden:

  • im Datenmodell,
  • im SQL-Schema,
  • in der API,
  • im DAO,
  • in Formularen,
  • in administrativen Ansichten.

Dadurch wird die Beziehung an einer Stelle beschrieben, statt unabhängig in jeder Anwendungsschicht.

Datenbeschränkungen

Das Modell kann auch Beschränkungen für Daten definieren.

Beispiel:

--constraints=<<CONSTRAINTSEOF
    unique:add --fields=lang,slug
CONSTRAINTSEOF

bedeutet, dass die Kombination:

lang + slug

eindeutig sein muss.

Dadurch kann derselbe Slug für verschiedene Sprachen gespeichert werden:

pl / architektura
en / architecture
de / architektur

und gleichzeitig wird verhindert, dass zwei Datensätze mit derselben Sprache und demselben Slug erstellt werden.

Entitätsmodule

Eine Entität kann durch Module zusätzliches Verhalten erhalten.

Beispielsweise verwendet die Entität doc:

module:add --name=seo
module:add --name=ssr
module:add --name=crud

Module ermöglichen es, eine Entität zu erweitern, ohne die gesamte Implementierung wiederholen zu müssen.

In der Praxis kann das Entitätsmodell somit nicht nur ihre Daten beschreiben, sondern auch die Art und Weise, wie sie von der Anwendung verwendet werden soll.

SEO-Modul

Das Modul:

module:add --name=seo

fügt Verhalten im Zusammenhang mit Seitenmetadaten hinzu.

Bei einer Entität, die von content erbt, kann es unter anderem Folgendes verwenden:

title
description

um die für Suchmaschinen und Mechanismen zur Inhaltsfreigabe benötigten Informationen vorzubereiten.

Dadurch kann die grundlegende SEO-Unterstützung aus dem Modell hervorgehen, statt für jede Seite manuell implementiert zu werden.

SSR-Rendering

Das Modul:

module:add --name=ssr

ermöglicht es, einen Entitätsdatensatz mit einer öffentlichen Anwendungsroute zu verknüpfen.

Beispiel für die Dokumentation:

module:add \
    --name=ssr \
    --slug=doc \
    --route="/doc/{lang}/{slug}" \
    --property:list:list="title,description" \
    --property:list:details="title,body"

Die Definition gibt unter anderem Folgendes an:

  • den öffentlichen Pfad,
  • die Art der Datensatzidentifikation,
  • die für die Liste benötigten Felder,
  • die für die Detailansicht benötigten Felder.

Für ein Beispieldokument kann der resultierende Pfad wie folgt aussehen:

/doc/de/projektarchitektur

Das SSR-Modul kann anschließend die Entitätsdaten verwenden, um eine serverseitig gerenderte Seite vorzubereiten.

CRUD-Modul

Das Modul:

module:add --name=crud

bestimmt die Art der Behandlung einer Entität durch generierte administrative Operationen.

Beispiel:

module:add \
    --name=crud \
    --property:list:list="ver,lang,title" \
    --property:list:persist="title,slug,lang,description,body,ver"

property:list:list bestimmt die Eigenschaften, die bei der Darstellung der Datensatzliste verwendet werden.

property:list:persist bestimmt die Felder, die beim Erstellen und Aktualisieren eines Datensatzes verwendet werden.

Das Modul crud definiert den WebUI/API-Vertrag. Konsolenfelder werden separat mit crud_cli konfiguriert:

module:add \
    --name=crud_cli \
    --property:list:list="username,email" \
    --property:list:persist="password,role,username,email"

Die generierten Befehle behalten den Namespace crud:<entity>:*. crud_cli.list steuert die Felder von crud:<entity>:list, während crud_cli.persist die Flags zum Erstellen und Aktualisieren steuert. Der List-Befehl gibt ein JSON-Objekt pro Zeile aus und unterstützt --limit, --order-by, --order-direction und --search.

Erweiterbare Module

Goat ermöglicht außerdem die Definition eigener Modultypen.

Beispiel:

entity:module:def \
    --type=top_box_counter \
    --query:count=<<QUERYDEFEOF
        def --required
QUERYDEFEOF \
    --short_text:label=<<LABELDEFEOF
        def --required
LABELDEFEOF

Die Definition erstellt den Modultyp top_box_counter, der Folgendes erfordert:

  • die Abfrage count,
  • die Beschriftung label.

Darauf basierend können anschließend konkrete Module erstellt werden.

Beispiel:

module:add \
    --name=admin_counter \
    --type=top_box_counter \
    --label=Administrators \
    --query:count=<<QUERYEOF
        where:and --conditions=<<WHEREEOF
            eq --field=role --value=admin
        WHEREEOF
QUERYEOF

Dadurch lassen sich wiederverwendbare Anwendungselemente auf einer höheren Abstraktionsebene erstellen.

Anstatt einen Benutzerzähler jedes Mal im Backend und Frontend zu implementieren, kann seine Struktur als Modellmodul definiert werden.

Beispiel eines Dashboard-Moduls

Mehrere Module können zu einer größeren Struktur kombiniert werden.

Beispiel:

entity:module:def \
    --type=summary_cards \
    --modules:list=<<MODULESLISTEOF
        def --types=top_box_counter --required
MODULESLISTEOF

Anschließend kann die Entität user eine Reihe von Zählern erhalten:

module:add \
    --name=summary_cards \
    --type=summary_cards \
    --modules:list=<<SUMMARYCARDSOF

        module:add \
            --name=admin_counter \
            --type=top_box_counter \
            --label=Administrators \
            --query:count=<<QUERYEOF
                where:and --conditions=<<WHEREEOF
                    eq --field=role --value=admin
                WHEREEOF
            QUERYEOF

        module:add \
            --name=manager_counter \
            --type=top_box_counter \
            --label=Managers \
            --query:count=<<QUERYEOF
                where:and --conditions=<<WHEREEOF
                    eq --field=role --value=manager
                WHEREEOF
            QUERYEOF

        module:add \
            --name=user_counter \
            --type=top_box_counter \
            --label=Users \
            --query:count=<<QUERYEOF
                where:and --conditions=<<WHEREEOF
                    eq --field=role --value=user
                WHEREEOF
            QUERYEOF
SUMMARYCARDSOF

Das Modell beschreibt hier nicht nur die Datenstruktur, sondern auch ein Dashboard-Element und die zu seiner Befüllung benötigten Abfragen.

Dies zeigt, dass herd/_model.goat nicht nur dem Äquivalent einer Datenbankschemadefinition entspricht. Es kann auch eine höhere Ebene des Anwendungsverhaltens beschreiben.

Unabhängige Entitäten

Nicht jede Entität muss von content erben.

Ein Beispiel ist eine Galerie:

entity:add \
    --name gallery \
    --label title \
    --base base_entity

Ihre Eigenschaften können wie folgt aussehen:

--properties=<<PROPERTIESEOF
    def --write=admin,manager --read=user --list

    add --name=title \
        --type=nice_name \
        --doc="Title is a short heading that describes the content."

    def --write=admin,manager --read=user

    add --name=slug \
        --type=web_slug \
        --unique \
        --doc="Slug is a URL-friendly name for the content."

    add --name=files \
        --type=file_set \
        --doc="Files for the gallery"
PROPERTIESEOF

Dies zeigt, dass das Modell sowohl auf einer gemeinsamen Basis aufbauende Entitäten als auch vollständig unabhängige Domänenstrukturen beschreiben kann.

Codegenerierung

Nach einer Änderung:

herd/_model.goat

starte den Generator:

goat re

oder direkt:

go run ./scripts re

Der Generator liest das aktuelle Modell und erstellt die daraus resultierenden Änderungen in der Anwendung.

Der Prozess lässt sich vereinfacht wie folgt darstellen:

herd/_model.goat
        ↓
herd/gen.goat
        ↓
Vorlagen
        ↓
Generator
        ↓
Anwendungscode

Generiert werden können unter anderem:

  • Modelle,
  • Repositories,
  • APIs,
  • CLI-Befehle,
  • Frontend,
  • SQL-Migrationen.

Das grundlegende Datenbankschema wird außerdem generiert nach:

app/static/raw/data/sql/migrations/0001_schema.sql

Konfiguration des Generators

Vor größeren Änderungen an der Generierungsweise sollte Folgendes geprüft werden:

herd/gen.goat

Die Datei beschreibt den Generierungsprozess, die verwendeten Vorlagen sowie die Orte, an die deren Ergebnisse geschrieben werden.

Wenn du die Generierung vieler ähnlicher Elemente ändern möchtest, ist es in der Regel besser, die entsprechende Vorlage anzupassen, als dieselbe Änderung manuell in vielen Dateien zu wiederholen.

Vereinfacht gesagt:

_model.goat

legt fest, was generiert werden soll,

während:

herd/gen.goat
herd/gen/templates/

festlegen, wie das Ergebnis aussehen soll.

Generierter Code kann bearbeitet werden

Der von Goat erzeugte Code bleibt gewöhnlicher Quellcode des Projekts.

Du kannst ihn:

  • ändern,
  • refaktorieren,
  • erweitern,
  • um eigene Logik ergänzen,
  • wie jeden anderen Code committen.

Der Generator soll die Arbeit beschleunigen und den Benutzer nicht in seinem Generierungsmodell einschränken.

Daher muss nicht jede Änderung in herd/_model.goat abgebildet werden.

Wenn du eine einmalige, für eine bestimmte Funktionalität spezifische Anpassung benötigst, kann die direkte Bearbeitung des generierten Codes die einfachste Lösung sein.

Wenn du jedoch bemerkst, dass du dieselbe Änderung wiederholt ausführst, lohnt es sich, sie eine Ebene höher zu verschieben — in das Modell oder die Generatorvorlage.

Wann sollte das Modell geändert werden?

Ändere herd/_model.goat, wenn die Anpassung die Struktur oder das Verhalten der Domäne betrifft.

Beispiele:

  • Hinzufügen einer Entität,
  • Hinzufügen einer Eigenschaft,
  • Ändern einer Beziehung,
  • Hinzufügen einer Einschränkung,
  • Ändern von Berechtigungen,
  • Hinzufügen eines Moduls,
  • Ändern der CRUD-Konfiguration,
  • Ändern der öffentlichen Route einer Entität.

Beispiel:

add \
    --name=published_at \
    --type=datetime

Wenn published_at ein Element des Domänenmodells ist, sollte es genau hier beschrieben werden.

Wann sollte die Vorlage geändert werden?

Ändere die Generatorvorlage, wenn die Änderung alle Elemente eines bestimmten Typs betreffen soll.

Beispiele:

  • alle Formulare sollen eine zusätzliche Struktur erhalten,
  • jeder generierte Endpoint soll einen neuen Mechanismus verwenden,
  • alle Entitäten sollen zusätzlichen Hilfscode erhalten,
  • du möchtest die Konvention des generierten Frontends ändern.

Solche Änderungen sollten am besten in:

herd/gen/templates/

vorgenommen werden, anstatt viele generierte Dateien einzeln zu korrigieren.

Wann sollte der Code direkt bearbeitet werden?

Die direkte Bearbeitung ist angebracht, wenn die Änderung spezifisch für einen konkreten Fall ist.

Beispielsweise:

  • ungewöhnliche Geschäftslogik,
  • zusätzliche Integration,
  • spezieller Endpoint,
  • besonderes Verhalten eines einzelnen Formulars,
  • komplexe Abfrage,
  • manuelle Optimierung,
  • benutzerdefiniertes UI-Element.

Es besteht keine Notwendigkeit, den Generator nur deshalb zu verkomplizieren, um eine einzelne Ausnahme zu behandeln.

Ein gutes Kriterium ist die Frage:

Beschreibt diese Änderung eine Projektregel oder eine Ausnahme, die für eine einzelne Funktionalität charakteristisch ist?

Regeln sollten in das Modell oder den Generator übernommen werden. Ausnahmen können direkt im Code verbleiben.

Sicherer Änderungsprozess

Eine typische Modelländerung sieht wie folgt aus:

Änderung von _model.goat
      ↓
goat re
      ↓
git diff
      ↓
manuelle Nachbearbeitung des Codes
      ↓
Tests
      ↓
Commit

In der Praxis:

# ändere das Modell
vim herd/_model.goat

# generiere die Änderungen
goat re

# prüfe das Ergebnis
git diff

# führe die Tests aus
goat run:script --path=herd/test.goat

git diff ist besonders wichtig, da es ermöglicht, den tatsächlichen Umfang der vom Generator vorbereiteten Änderungen zu sehen.

Behandle die Generierung nicht als Operation, deren Ergebnis ohne Kontrolle übernommen werden sollte.

Der Generator bereitet den Code vor, aber die endgültige Entscheidung über die Änderung liegt beim Entwickler.

Modell und bestehende Datenbank

Eine Modelländerung kann eine Änderung des generierten SQL-Schemas verursachen.

Das bedeutet nicht automatisch, dass sie für eine bestehende Datenbank sicher ist.

Beispielsweise:

Änderung von _model.goat
      ↓
Änderung des Zielschemas

ist nicht dasselbe wie:

sichere Migration bestehender Daten

Besondere Aufmerksamkeit erfordern unter anderem:

  • das Entfernen von Feldern,
  • Typänderungen,
  • das Hinzufügen erforderlicher Felder,
  • Änderungen von Beziehungen,
  • neue Einschränkungen,
  • Änderungen von Indizes.

Überprüfe vor der Bereitstellung einer solchen Änderung das generierte SQL und bereite einen geeigneten Migrationspfad vor.

Das Modell als Schicht für KI

Das deklarative Anwendungsmodell bietet bei der Arbeit mit KI einen zusätzlichen Vorteil.

In vielen Fällen muss das Modell nicht separat analysieren:

Go-Modell
DAO
DTO
API
Migrationen
Formular
Angular-Ansicht

Stattdessen kann es eine einzige Definition ändern:

herd/_model.goat

und anschließend Goat die Folgen dieser Änderung generieren lassen.

Beispielsweise:

„Dokument sollte ein Veröffentlichungsdatum besitzen“
                ↓
KI ändert _model.goat
                ↓
goat re
                ↓
Aktualisierung der erforderlichen Schichten

Dies reduziert die für die Analyse erforderliche Code-Menge und begrenzt das Risiko, eine der Schichten zu übersehen.

Nicht jede Änderung sollte jedoch über das Modell erfolgen. Wenn die Aufgabe einen einzelnen Teil der Implementierung betrifft, kann eine direkte Bearbeitung des Codes effizienter sein.

Beispielmodell

Der folgende Ausschnitt zeigt einige grundlegende Möglichkeiten des Goat-Modells:

# App metadata
app --name goat \
    --version 0.0.1 \
    --path app \
    --go-prefix code.pozoga.eu/spozoga/goatcms.com \
    --terminal-prefix APP \
    --default-language=en \
    --supported-languages=pl,en,de

# Roles
role:add --name=admin --super
role:add --name=manager
role:add --name=user

# Dashboard module types
entity:module:def --type=top_box_counter --query:count=<<QUERYDEFEOF
    def --required
QUERYDEFEOF --short_text:label=<<LABELDEFEOF
    def --required
LABELDEFEOF

entity:module:def --type=summary_cards --modules:list=<<MODULESLISTEOF
    def --types=top_box_counter --required
MODULESLISTEOF

# User
entity:overwrite --name=user --base=user_base_entity --system=<<ENTITYSYSTEMEOF
    module:add --name=summary_cards --type=summary_cards --modules:list=<<SUMMARYCARDSOF
        module:add --name=admin_counter --type=top_box_counter --label=Administrators --query:count=<<QUERYEOF
            where:and --conditions=<<WHEREEOF
                eq --field=role --value=admin
            WHEREEOF
        QUERYEOF

        module:add --name=manager_counter --type=top_box_counter --label=Managers --query:count=<<QUERYEOF
            where:and --conditions=<<WHEREEOF
                eq --field=role --value=manager
            WHEREEOF
        QUERYEOF

        module:add --name=user_counter --type=top_box_counter --label=Users --query:count=<<QUERYEOF
            where:and --conditions=<<WHEREEOF
                eq --field=role --value=user
            WHEREEOF
        QUERYEOF
    SUMMARYCARDSOF

    module:add --name=crud \
        --property:list:list="username,email" \
        --property:list:persist="role,username,email"
    module:add --name=crud_cli \
        --property:list:list="username,email" \
        --property:list:persist="password,role,username,email"
ENTITYSYSTEMEOF

# Base content entity
entity:base:add --name content --label slug --base base_entity --properties=<<PROPERTIESEOF
    def --write=admin,manager --read=user --list

    add --name=title \
        --type=web_title \
        --doc="Title is a short heading that describes the content."

    add --name=slug \
        --type=web_slug \
        --required \
        --doc="Slug is a URL-friendly name for the content."

    add --name=lang \
        --type=language \
        --required \
        --doc="Language of the current content."

    def --write=admin,manager --read=user

    add --name=description \
        --type=seo_description \
        --doc="Short description for SEO and social media."

    add --name=body \
        --type=block_content \
        --doc="Body contains the content data structured into blocks."
PROPERTIESEOF --relations=<<RELATIONSEOF
    def --onremove

    add --name=owner \
        --to=user \
        --doc="Owner is the author of the article."
RELATIONSEOF --constraints=<<CONSTRAINTSEOF
    unique:add --fields=lang,slug
CONSTRAINTSEOF

# Page
entity:extended --name page --base content --doc=<<DOCEOF
    Pages are the main sections of the website.
    They contain essential content that rarely changes and
    plays an important role in the overall structure and context of the website.
DOCEOF --system=<<ENTITYSYSTEMEOF
    module:add --name=seo

    module:add --name=ssr \
        --slug=page \
        --property:list:list="title,description" \
        --property:list:details="title,body"

    module:add --name=crud \
        --property:list:list="title,description" \
        --property:list:persist="title,slug,lang,description,body"
    module:add --name=crud_cli \
        --property:list:list="title,description" \
        --property:list:persist="title,slug,lang,description,body"
ENTITYSYSTEMEOF

# Documentation
entity:extended --name doc --base content --doc=<<DOCEOF
    The document contains comprehensive project documentation,
    including available commands, configuration details,
    usage examples and other information required to work
    with the project effectively.
DOCEOF --properties=<<PROPERTIESEOF
    def --write=admin,manager --read=user --list

    add --name=ver \
        --type=short_text \
        --doc="Goat CLI version number for the page"
PROPERTIESEOF --system=<<ENTITYSYSTEMEOF
    module:add --name=seo

    module:add --name=ssr \
        --slug=doc \
        --route="/doc/{lang}/{slug}" \
        --property:list:list="title,description" \
        --property:list:details="title,body"

    module:add --name=crud \
        --property:list:list="ver,lang,title" \
        --property:list:persist="title,slug,lang,description,body,ver"
    module:add --name=crud_cli \
        --property:list:list="ver,lang,title" \
        --property:list:persist="title,slug,lang,description,body,ver"
ENTITYSYSTEMEOF

# Gallery
entity:add --name gallery --label title --base base_entity --properties=<<PROPERTIESEOF
    def --write=admin,manager --read=user --list

    add --name=title \
        --type=nice_name \
        --doc="Title is a short heading that describes the content."

    def --write=admin,manager --read=user

    add --name=slug \
        --type=web_slug \
        --unique \
        --doc="Slug is a URL-friendly name for the content."

    add --name=files \
        --type=file_set \
        --doc="Files for the gallery"
PROPERTIESEOF

Dieses Beispiel zeigt die wichtigste Idee des Goat-Modells: Eine einzige deklarative Definition kann nicht nur die Datenbank beschreiben, sondern auch Berechtigungen, Beziehungen, CRUD, Routing, SEO, SSR und Elemente der Benutzeroberfläche.

Wichtigste Regel

Das Modell sollte diejenigen Elemente der Anwendung übernehmen, die sich wiederholen und deklarativ beschreiben lassen.

Der Generator übersetzt sie in eine konkrete Implementierung, und der generierte Code bleibt vollständig unter der Kontrolle des Entwicklers.

In der Praxis kannst du auf drei Ebenen arbeiten:

Modell — wenn die Änderung die Struktur und Regeln der Anwendung betrifft,

Generator und Vorlagen — wenn du die Art ändern möchtest, wie eine ganze Klasse von Elementen erstellt wird,

Anwendungscode — wenn du eine individuelle Implementierung benötigst.

Es geht nicht darum, um jeden Preis möglichst große Teile des Projekts zu generieren.

Goat soll das generieren, was sich wiederholt, damit mehr Zeit für Code bleibt, der die Anwendung tatsächlich auszeichnet.