Data model and code generation

The file:

herd/_model.goat

describes the application structure and is the primary source of information used by the Goat generator.

The model defines, among other things:

  • application metadata,
  • supported languages,
  • user roles,
  • entities,
  • entity properties,
  • relationships,
  • data constraints,
  • read and write permissions,
  • modules assigned to entities,
  • CRUD behavior,
  • SEO and SSR elements.

Based on this information, Goat can generate consistent components across multiple application layers, including:

  • Go models,
  • DAOs and repositories,
  • DTOs,
  • APIs,
  • CRUD commands,
  • SQL migrations,
  • forms,
  • lists and administrative views,
  • frontend components.

The main benefit is not merely generating files. The model makes it possible to describe a given structure once and then use that definition across many parts of the application.

Instead of manually synchronizing the backend, database, API, and frontend, you can modify the model and let the generator prepare the resulting changes.

The model as an application description

herd/_model.goat should primarily describe the intent and structure of the application, rather than implementation details of every generated file.

In simplified form:

_model.goat
    ↓
domain model
    ↓
generator
    ↓
backend + database + API + frontend

For example, a field definition:

add --name=title --type=web_title

can affect not only the backend model, but also how data is stored, generated DTOs, forms, and views.

This means that the same information does not need to be declared repeatedly across different technologies.

Application metadata

The model begins with the basic application configuration:

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

The definition specifies, among other things:

  • the application name,
  • version,
  • project path,
  • Go module prefix,
  • prefix used by the terminal,
  • default language,
  • list of supported languages.

This information can later be used by the generator and individual application modules.

Roles and permissions

Roles are defined directly in the model.

Example:

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

In this case, the application has three roles:

  • admin,
  • manager,
  • user.

The flag:

--super

indicates a role with extended system permissions.

Roles can later be used when defining access to entity properties.

Example:

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

allows you to specify which roles can modify data and which can read it.

This allows basic access rules to be defined at the model level rather than repeated independently in the API, forms, and backend.

Base entities

If several entities share a common structure, it is worth extracting it into a base entity.

An example is content:

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

A base entity can define shared:

  • properties,
  • relationships,
  • constraints,
  • access rules.

In the content project, it represents the basic structure of content published in the application.

It can then be used by:

  • pages,
  • documentation,
  • articles,
  • other content types.

Entity properties

Properties are defined within the --properties section.

Example:

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

Each property can specify, among other things:

  • name,
  • type,
  • required status,
  • presentation method,
  • access,
  • documentation,
  • additional generator behavior.

Property types carry more information than the column type itself in the database.

For example:

web_title
web_slug
language
seo_description
block_content
file_set

can also determine how data is validated, serialized, and presented in the interface.

Entity inheritance

Entities can extend base entities.

An example documentation entity:

entity:extended --name doc --base content

means that doc inherits the properties and behaviors defined for content.

This avoids repeating fields such as:

title
slug
lang
description
body

in every entity representing content.

An entity can also add its own properties.

Example:

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

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

This way, doc retains the entire content model while additionally storing the documentation version.

Relationships

Relationships between entities can be defined directly in the model.

Example:

--relations=<<RELATIONSEOF
    def --onremove

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

The definition specifies a owner relationship leading to the user entity.

The generator can use this information in many places:

  • the data model,
  • the SQL schema,
  • the API,
  • the DAO,
  • forms,
  • administrative views.

As a result, the relationship is described in one place instead of independently in every application layer.

Data constraints

The model can also define data constraints.

Example:

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

means that the combination:

lang + slug

must be unique.

This makes it possible to store the same slug for different languages:

pl / architektura
en / architecture
de / architektur

while preventing the creation of two records with the same language and slug.

Entity modules

An entity can gain additional behavior through modules.

For example, the doc entity uses:

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

Modules make it possible to extend an entity without having to repeat the entire implementation.

In practice, an entity model can therefore describe not only its data, but also how it should be used by the application.

SEO module

The module:

module:add --name=seo

adds behavior related to page metadata.

For an entity inheriting from content, it can use, among other things:

title
description

to prepare information required by search engines and content-sharing mechanisms.

As a result, basic SEO handling can be derived from the model instead of being implemented manually for every page.

SSR rendering

The module:

module:add --name=ssr

makes it possible to associate an entity record with a public application route.

Example for documentation:

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

The definition specifies, among other things:

  • the public path,
  • the record identification method,
  • fields required in the list,
  • fields required in the detail view.

For an example document, the resulting path may look like this:

/doc/en/project-architecture

The SSR module can then use the entity data to prepare a server-side rendered page.

CRUD module

The module:

module:add --name=crud

defines how the entity is handled by generated administrative operations.

Example:

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

property:list:list specifies the properties used when displaying the list of records.

property:list:persist specifies the fields used when creating and updating a record.

Based on this configuration, the generator can prepare the appropriate backend, CLI, and administration panel components.

Extensible modules

Goat also allows you to define your own module types.

Example:

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

The definition creates a module type top_box_counter, which requires:

  • a count query,
  • a label label.

You can then use it to create specific modules.

Example:

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

This makes it possible to build reusable application components at a higher level of abstraction.

Instead of implementing a user counter in the backend and frontend each time, you can define its structure as a model module.

Dashboard module example

Several modules can be combined into a larger structure.

Example:

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

The user entity can then be assigned a set of counters:

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

Here, the model describes not only the data structure, but also the dashboard component and the queries needed to populate it.

This shows that herd/_model.goat is not merely equivalent to a database schema definition. It can also describe a higher level of application behavior.

Independent entities

Not every entity must inherit from content.

A gallery is an example:

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

Its properties may look as follows:

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

This shows that a model can describe both entities based on a shared foundation and completely independent domain structures.

Code generation

After making changes:

herd/_model.goat

run the generator:

goat re

or directly:

go run ./scripts re

The generator reads the current model and prepares the resulting changes in the application.

The process can be simplified as follows:

herd/_model.goat
        ↓
herd/gen.goat
        ↓
templates
        ↓
generator
        ↓
application code

The following can be generated, among other things:

  • models,
  • repositories,
  • APIs,
  • CLI commands,
  • frontend,
  • SQL migrations.

The base database schema is also generated to:

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

Generator configuration

Before making major changes to the generation process, it is worth checking:

herd/gen.goat

The file describes the generation process, the templates used, and the locations where their outputs are placed.

If you want to change how many similar elements are generated, it is usually better to modify the relevant template than to manually repeat the same change across many files.

In simplified terms:

_model.goat

defines what should be generated,

while:

herd/gen.goat
herd/gen/templates/

define how the output should look.

Generated code can be edited

Code created by Goat remains regular project source code.

You can:

  • modify it,
  • refactor it,
  • extend it,
  • add your own logic,
  • commit it like any other code.

The generator is intended to speed up work, not lock the user into its generation model.

Therefore, not every change must be reflected in herd/_model.goat.

If you need a one-off modification specific to a particular feature, directly editing the generated code may be the simplest solution.

However, if you notice that you are making the same change repeatedly, it is worth moving it one level up—to the model or generator template.

When should you change the model?

Change herd/_model.goat when the modification concerns the structure or behavior of the domain.

Examples:

  • adding an entity,
  • adding a property,
  • changing a relationship,
  • adding a constraint,
  • changing permissions,
  • adding a module,
  • changing the CRUD configuration,
  • changing an entity's public route.

Example:

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

If published_at is an element of the domain model, it should be described here.

When should you change a template?

Modify the generator template when the change should apply to all elements of a given type.

Examples:

  • all forms should receive additional structure,
  • every generated endpoint should use a new mechanism,
  • all entities should receive additional helper code,
  • you want to change the convention of the generated frontend.

Such changes are best introduced in:

herd/gen/templates/

instead of modifying many generated files individually.

When should you edit code directly?

Direct editing is appropriate when the change is specific to a particular case.

For example:

  • unusual business logic,
  • additional integration,
  • a special endpoint,
  • exceptional behavior of a single form,
  • a complex query,
  • manual optimization,
  • a custom UI element.

There is no need to complicate the generator just to handle a single exception.

A good criterion is the question:

Does this change describe a project rule, or an exception specific to a single feature?

Rules are worth moving to the model or generator. Exceptions can remain directly in the code.

A safe change process

A typical model change looks as follows:

change to _model.goat
      ↓
goat re
      ↓
git diff
      ↓
manual code refinement
      ↓
tests
      ↓
commit

In practice:

# change the model
vim herd/_model.goat

# generate changes
goat re

# check the result
git diff

# run tests
goat run:script --path=herd/test.goat

git diff is particularly important because it allows you to see the actual scope of changes prepared by the generator.

Do not treat generation as an operation whose result should be accepted without review.

The generator prepares the code, but the final decision about the change remains with the developer.

The model and an existing database

Changing the model may result in a change to the generated SQL schema.

This does not automatically mean that it is safe for an existing database.

For example:

change to _model.goat
      ↓
change to the target schema

is not the same as:

safe migration of existing data

The following require particular attention:

  • removing fields,
  • type changes,
  • adding required fields,
  • relationship changes,
  • new constraints,
  • index changes.

Before deploying such a change, review the generated SQL and prepare an appropriate migration path.

The model as a layer for AI

A declarative application model provides an additional advantage when working with AI.

In many cases, the model does not need to analyze separately:

Go model
DAO
DTO
API
migrations
form
Angular view

Instead, it can modify a single definition:

herd/_model.goat

and then allow Goat to generate the consequences of that change.

For example:

"A document should have a publication date"
                ↓
AI changes _model.goat
                ↓
goat re
                ↓
update of the required layers

This reduces the amount of code that needs to be analyzed and limits the risk of overlooking one of the layers.

However, not every change should be made through the model. If a task concerns a single implementation fragment, direct code editing may be more effective.

Example model

The following fragment shows several basic capabilities of the Goat model:

# 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="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"
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"
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

This example demonstrates the key idea behind the Goat model: a single declarative definition can describe not only the database, but also permissions, relationships, CRUD, routing, SEO, SSR, and interface elements.

The most important principle

The model should take over those application elements that are repetitive and can be described declaratively.

The generator translates them into a concrete implementation, while the generated code remains fully under the developer's control.

In practice, you can work at three levels:

model — when the change concerns the application's structure and rules,

generator and templates — when you want to change how an entire class of elements is created,

application code — when you need an individual implementation.

The goal is not to generate as much of the project as possible at any cost.

Goat should generate what is repetitive, so that more time can be devoted to the code that truly distinguishes the application.