Maestros (Controllers)

Overview

In X-Go, controllers are called Maes (short for Maestro). A Mae orchestrates a single business operation by executing a defined sequence of steps. Each Mae handles one coherent action — creating a resource, listing records, editing an entity, or performing a complex workflow.

Maes are protocol-agnostic. They receive a MaeIn input struct (free of HTTP concerns) and return a MaeOut output struct, both defined in src/dat/maes/. Routes call the Mae and then render the output in whatever protocol is required (HTML, JSON, etc.).

Location: src/mae/

Naming Conventions

Mae names follow the pattern {Verb}{Entity}Mae. The six standard CRUD verbs always require an entity name:

Verb R/W Purpose Example
New Read Display an empty creation form NewGardenMae
Create Write Submit a creation form and persist the entity CreateGardenMae
Edit Read Display a pre-filled update form EditGardenTaskMae
Update Write Submit an update form and persist changes UpdateGardenTaskMae
Delete Write Remove an entity DeleteGardenMae
List Read Retrieve a paginated or filtered collection ListPlantsMae

New/Create and Edit/Update are read/write pairs: the first renders the form, the second handles the submission.

For non-CRUD operations, the pattern becomes {Verb}{Subject}Mae where the subject is not necessarily an entity. These verbs are optional and used as needed:

Verb R/W Purpose Example
Overview Read Show a summary or dashboard view OverviewTodayTasksMae
Display Read Show details for a single entity DisplayGardenMae
See Read Show a focused or contextual view of an entity SeeGardenHealthMae
Perform Write Execute a complex multi-step operation PerformSendingLettersMae

See and Perform can be used as a read/write pair when a contextual view precedes a complex action.

MaeIn and MaeOut Structures

Every Mae has a pair of data structs defined in src/dat/maes/. These are the explicit input/output contracts.

Location: src/dat/maes/

MaeIn

// src/dat/maes/create_garden_mae.go
package maes

// |@@| C

import (
    "my-app/src/dat/forms"
    "my-app/src/lib/in_context"
)

type CreateGardenMaeIn struct {
    Request CreateGardenMaeInReq
    Context in_context.InContext
}

type CreateGardenMaeInReq struct {
    Form forms.CreateGardenForm `form:"form"`
}

Context carries request metadata (authenticated user identity, session data) populated by the route before calling the Mae.

Request holds the raw input bound from the HTTP request — form values, query parameters, or JSON body fields.

MaeOut

type CreateGardenMaeOut struct {
    In       *CreateGardenMaeIn
    Response CreateGardenMaeOutRes
    Success  bool
    Ctx      context.Context
    Extra    out_extra.OutExtra
}

type CreateGardenMaeOutRes struct {
    Garden     *mdl.Garden
    FormBag    *form.FormBag
    Form       forms.CreateGardenForm
    FormFields []*presenter.FormField
}
  • Success — set to true by the Mae only after all steps complete successfully.
  • Extra — holds user-facing messages (success/failure notifications) added during step execution.
  • Response — contains all data the view or JSON renderer needs.

List Mae Pattern

List Maes include pagination and filter support in the request:

type ListGardensMaeIn struct {
    Request ListGardensMaeInReq
    Context in_context.InContext
}

type ListGardensMaeInReq struct {
    Page    int                 `form:"page" json:"page"`
    Filters []fetcher.RawFilter `form:"filters"`
    Columns []presenter.Column  `form:"columns"`
}

type ListGardensMaeOutRes struct {
    Gardens           []*mdl.Garden
    GardensPagination *fetcher.FetcherPagination
    List              struct {
        Docs    []*mdl.Garden
        Columns []*presenter.Column
    }
}

Delete Mae Pattern

Delete Maes only need an identifier in the request:

type DeleteGardenMaeIn struct {
    Request DeleteGardenMaeInReq
    Context in_context.InContext
}

type DeleteGardenMaeInReq struct {
    Id string `form:"id" json:"id"`
}

type DeleteGardenMaeOut struct {
    In       *DeleteGardenMaeIn
    Response DeleteGardenMaeOutRes
    Success  bool
    Ctx      context.Context
    Extra    out_extra.OutExtra
}

type DeleteGardenMaeOutRes struct{}

Mae Implementation

The Mae struct lives in src/mae/ and wires together its steps and dependencies via injection. It exposes a single Act method.

// src/mae/create_garden_mae.go
package mae

// |@@| C

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    "my-app/src/lib/sql_db"
    steps "my-app/src/mae/create_garden"
    "my-app/src/srv/form_validators"
    "my-app/src/srv/handlers"
    "my-app/src/srv/molders"
)

type CreateGardenMae struct {
    ConfigureFormStep          *steps.ConfigureFormStep
    CreateGardenFormMolder     *molders.CreateGardenFormMolder
    CreateGardenFormValidator  *form_validators.CreateGardenFormValidator
    GardenHandler              *handlers.GardenHandler
    Maestro                    *maestro.Maestro
    RecordEventStep            *steps.RecordEventStep
    SaveEntityStep             *steps.SaveEntityStep
    SqlDb                      *sql_db.SqlDb
    ValidateFormStep           *steps.ValidateFormStep
}

func (this *CreateGardenMae) Act(ctx context.Context, in *maes.CreateGardenMaeIn) (*maes.CreateGardenMaeOut, error) {
    out := &maes.CreateGardenMaeOut{In: in, Ctx: ctx}
    acts := []func(ctx context.Context, in *maes.CreateGardenMaeIn, out *maes.CreateGardenMaeOut) (bool, error){
        this.ConfigureFormStep.Act,
        this.ValidateFormStep.Act,
        this.SaveEntityStep.Act,
        this.RecordEventStep.Act,
    }
    out, err := maestro.ActOnActs(ctx, in, out, acts)
    if err != nil {
        return out, err
    }
    out.Success = true
    return out, nil
}

maestro.ActOnActs iterates the steps in order. If any step returns false or an error, execution stops immediately.

Steps

Each Mae’s steps are organized in a dedicated sub-package under src/mae/{action}_{entity}/. Steps are small, focused structs implementing a single part of the operation.

Location: src/mae/{action}_{entity}/

ConfigureFormStep

Prepares the response form and form fields for display. Runs on every request, including the initial GET before any data is submitted.

// src/mae/create_garden/configure_form_step.go
package steps

// |@@| F

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    "my-app/src/srv/form_presenters"
)

type ConfigureFormStep struct {
    CreateGardenFormPresenter *form_presenters.CreateGardenFormPresenter
    Maestro                   *maestro.Maestro
}

func (this *ConfigureFormStep) Act(ctx context.Context, in *maes.CreateGardenMaeIn, out *maes.CreateGardenMaeOut) (bool, error) {
    out.Response.Form = in.Request.Form
    out.Response.FormBag = this.Maestro.EmptyFormBag()
    out.Response.FormFields = this.CreateGardenFormPresenter.Setup(&out.Response.Form)
    return true, nil
}

ValidateFormStep

Runs form validation and adds a failure message if the form is invalid. Returning false halts execution — the route will re-render the form with validation errors.

// src/mae/create_garden/validate_form_step.go
package steps

// |@@| F

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    "my-app/src/srv/form_validators"
)

type ValidateFormStep struct {
    CreateGardenFormValidator *form_validators.CreateGardenFormValidator
    Maestro                   *maestro.Maestro
}

func (this *ValidateFormStep) Act(ctx context.Context, in *maes.CreateGardenMaeIn, out *maes.CreateGardenMaeOut) (bool, error) {
    bag, err := this.CreateGardenFormValidator.Validate(ctx, &in.Request.Form)
    out.Response.FormBag = bag
    if err != nil {
        this.Maestro.AddFailureMessage(&out.Extra, "Cannot submit form")
        return false, err
    }
    return true, nil
}

SaveEntityStep

Converts the form into an entity using the Molder, persists it via the Handler, and adds a success message.

// src/mae/create_garden/save_entity_step.go
package steps

// |@@| W

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    "my-app/src/mdl"
    "my-app/src/srv/handlers"
    "my-app/src/srv/molders"
)

type SaveEntityStep struct {
    CreateGardenFormMolder *molders.CreateGardenFormMolder
    GardenHandler          *handlers.GardenHandler
    Maestro                *maestro.Maestro
}

func (this *SaveEntityStep) Act(ctx context.Context, in *maes.CreateGardenMaeIn, out *maes.CreateGardenMaeOut) (bool, error) {
    var err error
    entity := &mdl.Garden{}
    entity, _ = this.CreateGardenFormMolder.ToEntity(&in.Request.Form, entity, in.Context.RequestPresence)
    out.Response.Garden, err = this.GardenHandler.Create(ctx, entity, nil)
    if err != nil {
        return false, err
    }
    this.Maestro.AddSuccessMessage(&out.Extra, "Garden created")
    return true, nil
}

RecordEventStep

Appends an audit event after a successful write operation.

// src/mae/create_garden/record_event_step.go
package steps

// |@@| F

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    "my-app/src/srv/handlers"
)

type RecordEventStep struct {
    EventHandler *handlers.EventHandler
    Maestro      *maestro.Maestro
}

func (this *RecordEventStep) Act(ctx context.Context, in *maes.CreateGardenMaeIn, out *maes.CreateGardenMaeOut) (bool, error) {
    event := this.Maestro.NewEvent(ctx)
    event.What = "create_garden"
    event.ResourceId = out.Response.Garden.Id
    event.ResourceType = "garden"
    this.EventHandler.MustCreate(ctx, event, nil)
    return true, nil
}

DeleteEntityStep

Fetches and removes an entity by ID.

// src/mae/delete_garden/delete_entity_step.go
package steps

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    "my-app/src/srv/fetchers"
    "my-app/src/srv/handlers"
)

type DeleteEntityStep struct {
    GardenFetcher *fetchers.GardenFetcher
    GardenHandler *handlers.GardenHandler
    Maestro       *maestro.Maestro
}

func (this *DeleteEntityStep) Act(ctx context.Context, in *maes.DeleteGardenMaeIn, out *maes.DeleteGardenMaeOut) (bool, error) {
    garden, exists, err := this.GardenFetcher.FindOneById(ctx, in.Request.Id, nil)
    if err != nil {
        return false, err
    }
    if !exists {
        return false, nil
    }
    err = this.GardenHandler.Delete(ctx, garden, nil)
    if err != nil {
        return false, err
    }
    this.Maestro.AddSuccessMessage(&out.Extra, "Garden deleted")
    return true, nil
}

Step Control Flow

Steps return (bool, error):

Return Meaning
true, nil Step succeeded — continue to the next step
false, nil Step decided to stop — execution halts, no error is propagated
false, err Step failed — execution halts, error is propagated to the route

Use the maestro helpers for common returns:

return maestro.Continue()   // (true, nil)
return this.Maestro.Stop()  // (false, nil)
return maestro.Unauthorized() // (false, error "Not authorized")

User Feedback via OutExtra

Maes communicate results to the user through out.Extra.Messages. The Maestro service provides helpers:

this.Maestro.AddSuccessMessage(&out.Extra, "Garden saved")
this.Maestro.AddFailureMessage(&out.Extra, "Cannot save garden")

The route reads out.Extra and renders these messages as flash notifications.

Modifying Maes

Why Modify a Mae

Maes evolve alongside the application’s business rules. Common reasons to touch an existing Mae:

  • New side effects — send a notification, trigger a follow-up action, or update a related entity after a write
  • Authorization — restrict an action to certain users before any business logic runs
  • Data enrichment — populate extra fields in the response for a view that now needs them
  • Changed validation — tighten or relax the rules applied before a save
  • Audit and observability — record more context in the event log

When to Modify a Mae vs. When to Create a New Mae Step

Modify an existing Mae when the operation is the same but its behavior needs to change or grow. Create a new Mae step when you need a distinct operation — a different action, a different entity, or a meaningfully different flow.

Within an existing Mae, add a new step when the new logic is substantial or testable on its own. Change an existing step when the modification is small and tightly coupled to that step’s existing purpose.

How to Create a New Mae

Creating a new Mae involves three layers: the data contract, the Mae itself, and its steps.

1. Define MaeIn and MaeOut in src/dat/maes/

Create a new file named after your Mae. Define what the route will pass in and what the Mae will return.

// src/dat/maes/publish_garden_mae.go
package maes

// |@@| C

import (
    "my-app/src/dat/forms"
    "my-app/src/lib/in_context"
)

type PublishGardenMaeIn struct {
    Request PublishGardenMaeInReq
    Context in_context.InContext
}

type PublishGardenMaeInReq struct {
    Id   string                    `form:"id" json:"id"`
    Form forms.PublishGardenForm   `form:"form"`
}

type PublishGardenMaeOut struct {
    In       *PublishGardenMaeIn
    Response PublishGardenMaeOutRes
    Success  bool
    Ctx      context.Context
    Extra    out_extra.OutExtra
}

type PublishGardenMaeOutRes struct {
    Garden     *mdl.Garden
    FormBag    *form.FormBag
    Form       forms.PublishGardenForm
    FormFields []*presenter.FormField
}

2. Create the Mae struct in src/mae/

Create src/mae/publish_garden_mae.go. Declare all dependencies as exported fields and implement Act by listing the steps in execution order.

// src/mae/publish_garden_mae.go
package mae

// |@@| C

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    steps "my-app/src/mae/publish_garden"
    "my-app/src/srv/form_validators"
    "my-app/src/srv/handlers"
    "my-app/src/srv/molders"
)

type PublishGardenMae struct {
    ConfigureFormStep         *steps.ConfigureFormStep
    PublishGardenFormMolder   *molders.PublishGardenFormMolder
    PublishGardenFormValidator *form_validators.PublishGardenFormValidator
    GardenHandler             *handlers.GardenHandler
    Maestro                   *maestro.Maestro
    RecordEventStep           *steps.RecordEventStep
    PublishEntityStep         *steps.PublishEntityStep
    ValidateFormStep          *steps.ValidateFormStep
}

func (this *PublishGardenMae) Act(ctx context.Context, in *maes.PublishGardenMaeIn) (*maes.PublishGardenMaeOut, error) {
    out := &maes.PublishGardenMaeOut{In: in, Ctx: ctx}
    acts := []func(ctx context.Context, in *maes.PublishGardenMaeIn, out *maes.PublishGardenMaeOut) (bool, error){
        this.ConfigureFormStep.Act,
        this.ValidateFormStep.Act,
        this.PublishEntityStep.Act,
        this.RecordEventStep.Act,
    }
    out, err := maestro.ActOnActs(ctx, in, out, acts)
    if err != nil {
        return out, err
    }
    out.Success = true
    return out, nil
}

3. Create the step sub-package in src/mae/publish_garden/

Each step lives in its own file within the sub-package. Start with the steps your Mae needs and add more as the operation grows.

// src/mae/publish_garden/publish_entity_step.go
package steps

// |@@| W

import (
    "context"
    "my-app/src/dat/maes"
    "my-app/src/lib/maestro"
    "my-app/src/srv/fetchers"
    "my-app/src/srv/handlers"
)

type PublishEntityStep struct {
    GardenFetcher *fetchers.GardenFetcher
    GardenHandler *handlers.GardenHandler
    Maestro       *maestro.Maestro
}

func (this *PublishEntityStep) Act(ctx context.Context, in *maes.PublishGardenMaeIn, out *maes.PublishGardenMaeOut) (bool, error) {
    garden, exists, err := this.GardenFetcher.FindOneById(ctx, in.Request.Id, nil)
    if err != nil {
        return false, err
    }
    if !exists {
        return this.Maestro.Stop()
    }
    out.Response.Garden, err = this.GardenHandler.Publish(ctx, garden, nil)
    if err != nil {
        return false, err
    }
    this.Maestro.AddSuccessMessage(&out.Extra, "Garden published")
    return maestro.Continue()
}

4. Wire it up

Register the new Mae and its steps in the dependency injection container so the router can resolve them. Follow the same registration pattern used by existing Maes in your application’s wiring layer.

File layout for the new Mae:

src/
├── dat/
│   └── maes/
│       └── publish_garden_mae.go      # MaeIn / MaeOut structs
├── mae/
│   ├── publish_garden_mae.go          # Mae struct and Act method
│   └── publish_garden/
│       ├── configure_form_step.go     # Prepare form fields
│       ├── validate_form_step.go      # Validate form input
│       ├── publish_entity_step.go     # Core business logic
│       └── record_event_step.go       # Audit logging

🪪 For Blue-Lila Employees : How to Add a Step to an existing Mae

Add a new step in gen-x project’s depicter with Depicter.AddStepToMae(ctx, name, data, position, maeName).

This will create a new step service and attach it to the named Maestro at the given position.

How to Extend MaeIn or MaeOut

When a step needs to pass or receive data that isn’t currently in the contract, extend the relevant struct in src/dat/maes/.

Adding a request field (e.g. a flag passed from the route):

type CreateGardenMaeInReq struct {
    Form        forms.CreateGardenForm `form:"form"`
    SendWelcome bool                   `form:"send_welcome" json:"send_welcome"` // added
}

Adding a response field (e.g. a count the view now needs):

type CreateGardenMaeOutRes struct {
    Garden       *mdl.Garden
    FormBag      *form.FormBag
    Form         forms.CreateGardenForm
    FormFields   []*presenter.FormField
    TotalGardens int  // added — populated by a step
}

Steps read from in.Request and write to out.Response, so any field added here is immediately accessible to all steps in the Mae.

Ordering Guidelines

When inserting a new step, use this order as a reference:

  1. Authorization — gate access before anything else
  2. ConfigureForm — always prepare the form, including on POST so the view can re-render on failure
  3. ValidateForm — stop early if input is invalid
  4. SaveEntity / DeleteEntity — write to the database
  5. Side effects — notifications, follow-up actions, cache invalidation
  6. RecordEvent — audit trail last, so the event is only written on full success

Mae File Structure Summary

For a CreateGarden controller, the complete file layout is:

src/
├── dat/
│   └── maes/
│       └── create_garden_mae.go      # MaeIn / MaeOut structs
├── mae/
│   ├── create_garden_mae.go          # Mae struct and Act method
│   └── create_garden/
│       ├── configure_form_step.go    # Prepare form fields
│       ├── validate_form_step.go     # Validate form input
│       ├── save_entity_step.go       # Persist entity
│       └── record_event_step.go      # Audit logging