Concepts Overview
The mental model behind Grove: projects, modules, the action-event-record pipeline, and how workflows fit in. Understanding these concepts makes every other page in this documentation click.
Prerequisites: Hello World or 5-Minute Tutorial. What you'll learn: How Grove's core abstractions relate to each other.
The Hierarchy
Project (grove.toml)
+-- Module (directory)
+-- Record -- the entity being modeled
+-- Events -- immutable facts
+-- Actions -- entry points for change
+-- Validators -- reusable rules
+-- Types & Enums -- data shapes
+-- Tests -- built-in test runner
Project
A project is a repository with a grove.toml at the root. The project_id becomes the first segment of all resource identifiers.
project_id = "acme"
Module
A directory containing .grove files. All files in the directory are merged into one scope -- you can split declarations across files for organization. The directory name is the module name.
acme/
grove.toml # project_id = "acme"
orders/
module.grove # record, events, actions
types.grove # type and enum declarations
orders_test.grove # tests (excluded from compilation)
shipping/
module.grove
Area 1: Aggregates
The aggregate pattern is the core of Grove. Every state change follows this pipeline:
Action → validate → emit Event(s) → apply to Record
Record
The current state of an entity. One record per module. Fields have types, optional markers, and default values.
record {
kind "order"
title: String
status: String = "draft"
items: List<OrderItem>
total: Decimal?
}
Records also contain invariants (conditions that must always hold) and state machines (valid field transitions).
Event
An immutable fact describing something that happened. Events are stored permanently and can be replayed to reconstruct any past state.
event order_placed {
customer_id: Id
items: List<OrderItem>
total: Decimal
}
Action
The only way to change state. Actions receive input (params), validate it, and emit events in their apply block.
action place_order {
customer_id: Id
items: List<OrderItem>
validate has_items {
params.items.len > 0
}
apply {
let total = params.items.map(|i| i.price * i.quantity).sum()
emit order_placed {
customer_id: params.customer_id
items: params.items
total: total
}
}
}
The Flow
- A request arrives with an action name, parameters, and metadata
- Validation runs -- both reusable validators and inline validate blocks
- The apply block executes, emitting one or more events
- Each event is stored and applied to the record
- Invariants are checked against the new record state
- State machine transitions are verified
- The new record state is returned
If any step fails -- validation, invariant violation, invalid state transition -- the entire operation is rejected. No events are stored, no state changes.
Area 2: Workflows
Workflows extend Grove beyond single-entity operations into multi-step, multi-service orchestration.
Workflow
A directed acyclic graph (DAG) of activities. Nodes are units of work; edges define execution order.
workflow fulfill_order v1 {
node validate = validate_inventory v1
node charge = charge_payment v1
node ship = create_shipment v1
edge validate -> charge
edge charge -> ship
}
Activity
A unit of work with input, data operations, and output. Activities can call external services, publish messages, invoke other modules, or query databases.
activity charge_payment v1 {
data charge {
fetch stripe.create_charge {
amount: input.amount
currency: input.currency
}
}
apply(input: ChargeInput) -> ChargeResult {
return { charge_id: charge.id, status: charge.status }
}
}
Service
External API contracts. Services declare operations with their inputs, outputs, and authentication.
Trigger
Starts a workflow when an event occurs in a module.
Schedule
Starts a workflow on a cron schedule.
Type System
Grove uses a nominal type system -- types with the same fields but different names are not interchangeable.
| Category | Types |
|---|---|
| Scalars | Int, Decimal, Bool, String, Id, Date, Time, DateTime, Duration, Epoch |
| Composites | List<T>, Map<K, V>, T? (optional) |
| Custom | type Name { ... }, enum Name { ... } |
Root Objects
Expressions in Grove always start from a root object that depends on context:
| Context | Available roots |
|---|---|
Action apply | params, record, meta |
Event apply | event, record, meta |
| Validate | params, record |
| Invariant | record |
Activity apply | input, data block names |
| Test assertion | record, events |
Resource Naming
Every construct has a global name with four parts: project::module::name::version.
acme::orders::place_order::v1
In .grove source files, you use . separators and space-separated versions:
trigger on_placed from orders.order_placed { ... }
node charge = charge_payment v1
The compiler maps these to their full resource names internally.
What's Next
| Topic | Page |
|---|---|
| Build something real | Build a Task Tracker |
| Learn validation deeply | Validation & State Machines |
| Explore types and expressions | Expressions Deep Dive |
| Run against a database | Database Backends |
| See the full grammar | Declaration Grammar |
Authentication and Authorization
Two more concepts round out the mental model, each with its own section in these docs:
- Authentication answers "who is making this request?" and produces a
ctx.principalvalue in your actions. See Authentication Overview. - Authorization answers "is this principal allowed to do X?" via
authorizehooks and optional Cedar policies. See Authorization Hooks.
You can build a complete Grove project without either — Manzano will run without authentication configured, and actions without authorize hooks are permitted by default.
See Also
- Authentication Overview -- The three-layer browser/edge/backend model
- LLM Cheat Sheet -- Quick reference for code generation
- CLI Reference -- All
grove-clicommands