Data model and code generation
The file:
herd/_model.goatdescribes 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 + frontendFor example, a field definition:
add --name=title --type=web_titlecan 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,deThe 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=userIn this case, the application has three roles:
admin,manager,user.
The flag:
--superindicates a role with extended system permissions.
Roles can later be used when defining access to entity properties.
Example:
def --write=admin,manager --read=userallows 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_entityA 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."
PROPERTIESEOFEach 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_setcan 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 contentmeans that doc inherits the properties and behaviors defined for content.
This avoids repeating fields such as:
title
slug
lang
description
bodyin 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"
PROPERTIESEOFThis 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."
RELATIONSEOFThe 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
CONSTRAINTSEOFmeans that the combination:
lang + slugmust be unique.
This makes it possible to store the same slug for different languages:
pl / architektura
en / architecture
de / architekturwhile 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=crudModules 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=seoadds behavior related to page metadata.
For an entity inheriting from content, it can use, among other things:
title
descriptionto 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=ssrmakes 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-architectureThe SSR module can then use the entity data to prepare a server-side rendered page.
CRUD module
The module:
module:add --name=cruddefines 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
LABELDEFEOFThe definition creates a module type top_box_counter, which requires:
- a
countquery, - a
labellabel.
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
QUERYEOFThis 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
MODULESLISTEOFThe 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
SUMMARYCARDSOFHere, 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_entityIts 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"
PROPERTIESEOFThis 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.goatrun the generator:
goat reor directly:
go run ./scripts reThe 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 codeThe 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.sqlGenerator configuration
Before making major changes to the generation process, it is worth checking:
herd/gen.goatThe 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.goatdefines 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=datetimeIf 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
↓
commitIn 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.goatgit 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 schemais not the same as:
safe migration of existing dataThe 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 viewInstead, it can modify a single definition:
herd/_model.goatand 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 layersThis 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"
PROPERTIESEOFThis 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.