Skip to content

Commit 441c264

Browse files
Feature/documentation blog (#20)
* feat: Add gh-pages blog with introductory post and index page - Add generated client post - Add events post for state machine - Add pluggable storage post - Add post covering reconciliation with events and pluggable engine - Add post about re-generating to obtain new features - Add post about putting standards in code rather than convention --------- Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
1 parent 3fdb8fb commit 441c264

12 files changed

+409
-2
lines changed

bin/fabrica

-7.24 MB
Binary file not shown.

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SPDX-License-Identifier: MIT
1414
- **[Getting Started Guide](guides/getting-started.md)** - Full tutorial
1515
- **[Architecture Overview](reference/architecture.md)** - Framework design
1616
- **[API Reference](https://pkg.go.dev/github.com/openchami/fabrica)** - Go package docs
17+
- **[Blog](./blog/)** - Short, friendly posts with examples
1718

1819
## 📖 Documentation Structure
1920

docs/_config.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# SPDX-FileCopyrightText: 2025 OpenCHAMI Contributors
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
title: Fabrica Blog
6+
description: Simple guides introducing Fabrica with hands-on examples.
7+
# Use GitHub Pages default theme
8+
remote_theme: pages-themes/minimal@v0.2.0
9+
plugins:
10+
- jekyll-feed
11+
- jekyll-seo-tag
12+
- jekyll-remote-theme
13+
show_excerpts: true
14+
# Build from /docs folder
15+
markdown: kramdown
16+
kramdown:
17+
input: GFM
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 OpenCHAMI Contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
---
8+
layout: post
9+
title: "Meet the Generated Client"
10+
description: "How the CLI and Go library help you use your API the same way every time."
11+
---
12+
13+
APIs are easier to use when the client is predictable. You should not have to remember custom flags or one‑off scripts. Fabrica generates two clients that match your server: a CLI and a Go library. They share patterns and types, so your shell steps and your code read the same way.
14+
15+
## What you get
16+
17+
The CLI is generated from `pkg/codegen/templates/client/cmd.go.tmpl`. Commands are grouped by resource: list, get, create, update, patch, delete. Output can be tables or JSON, which makes it simple to script with tools like jq. Version‑specific commands appear only when a resource supports them, so the CLI is always in sync with your API.
18+
19+
The Go client library comes from `pkg/codegen/templates/client/client.go.tmpl`. Method names are predictable and typed: `Get<Device>`, `Create<Device>`, `Update<Device>`, `Patch<Device>`, and status‑only methods like `Update<Device>Status`. The client handles headers, content types, and patch formats for you. When API version headers are used, the client sets them.
20+
21+
## Under the hood
22+
23+
The CLI creates a typed client with `client.NewClient` and calls methods that mirror your resources. The methods issue HTTP requests to the server’s generated routes (`pkg/codegen/templates/server/routes.go.tmpl`) and unmarshal responses into your types from `pkg/resources/...`. Status operations go to `/status` endpoints to avoid spec conflicts.
24+
25+
When you enable spec versioning on a resource, the generator adds version helpers and subcommands. The Go client gets `List<Resource>Versions`, `Get<Resource>Version`, and `Delete<Resource>Version`, and the CLI nests them under `<resource> versions ...`. These come from the same templates, so you do not maintain them by hand.
26+
27+
## Trade‑offs and limits
28+
29+
The generated client is opinionated. It follows the server’s routes and types exactly. That keeps things consistent, but it is not a generic HTTP tool. If you need custom flows or experiments, you can wrap the client in your own package.
30+
31+
## Try it
32+
33+
```bash
34+
fabrica init myapp
35+
fabrica add resource Device
36+
fabrica generate
37+
go run ./cmd/client/ device create --spec '{"name":"dev-01","description":"demo"}' -o json
38+
```
39+
40+
## What to watch for in production
41+
42+
Prefer `-o json` when you need to script. Use the status methods when workers or controllers update state, so spec and status do not clash. If your API adds versioned endpoints, re‑generate so the client gains matching commands and types.
43+
44+
## Related reading
45+
46+
- Guide: `docs/guides/quickstart.md`
47+
- Example: `examples/01-basic-crud/`
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 OpenCHAMI Contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
---
8+
layout: post
9+
title: "Events as a Simple State Machine"
10+
description: "How Fabrica’s event system lets you model and drive resource state changes."
11+
---
12+
13+
Events let your API tell the world what changed. In Fabrica, that stream becomes more than a log. It is a simple way to model a resource’s state and move it forward. When a resource is created, updated, or deleted, the server publishes an event. When Status changes in a meaningful way, you can publish a condition change. Other parts of your system can subscribe and react.
14+
15+
## Under the hood
16+
17+
Generated handlers publish through helper functions in `pkg/events/events.go`. For local work you can use the in‑memory bus in `pkg/events/memory_bus.go`. The publish calls live next to handlers, not inside your business logic. Subscribers are normal functions that receive events and can call the generated client to update Status.
18+
19+
Think of a sensor that must be checked by a worker before it is ready. The user creates the sensor. The server saves it and publishes a created event. A subscriber hears that event, runs a check, and writes back a Status update that marks the sensor as ready or not. A ready condition produces its own event, and another subscriber may wake up and do the next step. You get a small, clear chain of actions without tight coupling.
20+
21+
This works well with the reconciliation pattern (`pkg/reconcile/controller.go`). A controller subscribes to events, enqueues work, and updates Status through the client’s status methods. Because Spec and Status live on different paths, workers do not conflict with users.
22+
23+
## Trade‑offs and limits
24+
25+
Events are best for edge‑triggered workflows and for integrating with outside systems. They are not a full workflow engine. Keep handlers small. Use reconciliation loops when you need to converge on a desired state.
26+
27+
## Try it
28+
29+
```bash
30+
fabrica init sensors --events --events-bus memory
31+
fabrica add resource Sensor
32+
fabrica generate
33+
go run ./cmd/client/ sensor create --spec '{"name":"s1","description":"first"}'
34+
```
35+
36+
## What to watch for in production
37+
38+
Choose a bus that fits your stack. The in‑memory bus is for local testing. If you use SQLite for examples, enable foreign keys (`?_fk=1`). Keep subscribers small and testable; they should call the client’s status methods, not reach into storage.
39+
40+
## Related reading
41+
42+
- Guide: `docs/guides/events.md`
43+
- Example: `examples/05-cloud-events/`
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 OpenCHAMI Contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
---
8+
layout: post
9+
title: "Pluggable storage in Fabrica: files to databases"
10+
description: "How the storage contract lets you swap file and database backends without changing your API, with a look at the FRU example."
11+
---
12+
13+
Fabrica generates REST services from Kubernetes‑style resources. The shape is always the same: APIVersion, Kind, Metadata, Spec, and Status. Handlers, routes, models, and the client are generated from templates. What you store those resources in is up to you. The storage layer is pluggable, so you can start with files and move to a database later without rewriting your API.
14+
15+
The key idea is separation. The handlers talk to a storage interface, not to disks or SQL directly. You can see that in `pkg/codegen/templates/server/handlers.go.tmpl`. The code calls storage helpers with typed resources and returns results to the client. The same handler code compiles whether you pick the file backend or the Ent (database) backend.
16+
17+
## What you get
18+
19+
You get a consistent API surface and a storage implementation that matches it. The file backend writes JSON objects to disk. It is simple, great for demos and local dev, and easy to inspect. The database backend uses Ent, a type‑safe ORM for Go. It brings relationships, indexes, and migrations. Both satisfy the same storage contract declared in `pkg/storage/interfaces.go`.
20+
21+
Nothing above storage needs to change. Validation and conditional requests keep working. Those come from middleware in the server templates. Events still publish lifecycle and condition changes (see `pkg/events/events.go`). Reconciliation, when enabled, still processes work (see `pkg/reconcile/controller.go`). Even the code generation knobs are the same. The generator reads your resources and features and emits the right code (see `pkg/codegen/generator.go` and the CLI hook in `cmd/fabrica/add.go`).
22+
23+
## How it works under the hood
24+
25+
The file backend lives in `pkg/codegen/templates/storage/file.go.tmpl`. It organizes data by resource kind and UID under a data directory. Create, update, list, and delete are plain file operations plus JSON encoding and decoding. If you enable spec version history for a resource, version snapshots are also files on disk in a versions folder.
26+
27+
The database backend uses Ent. The adapter is in `pkg/codegen/templates/storage/ent.go.tmpl`. The schemas for annotations, labels, and resources are in `pkg/codegen/templates/ent/schema/*.go.tmpl`. Generated servers open a database connection, run schema creation in development, and set the storage adapter. Handlers don’t know or care which backend you chose, because they call through the same interfaces.
28+
29+
The handlers template, `pkg/codegen/templates/server/handlers.go.tmpl`, wires requests to storage calls. It also makes the Spec vs. Status split explicit. Spec is what a user sets. Status is managed by the system. That separation is important because it means status updates do not mix with spec persistence logic. It also makes versioning possible to implement cleanly.
30+
31+
## Trade‑offs and limits
32+
33+
Files are simple and fast to get started with. They are also easy to back up and read during debugging. The trade‑off is querying and constraints. If you need strong relationships, transactions, or complex queries, a database is a better fit. Ent gives you typed queries, migration helpers, and schema‑as‑code.
34+
35+
Databases add operational work. You will think about migrations, connection strings, and indexes. The upside is predictable behavior at scale. The storage choice does not change your API, so you can migrate later without breaking clients.
36+
37+
Events and reconciliation do not depend on storage. They depend on the resource model. Event types and helpers are in `pkg/events/events.go`. The reconciliation controller is in `pkg/reconcile/controller.go`. You can enable or disable those features without touching storage code.
38+
39+
## Try it
40+
41+
Here is a tiny flow that shows the storage plug‑in with the generated client. It uses file storage so you can try it anywhere. Run the server in the background and call it from the client.
42+
43+
```bash
44+
fabrica init store-demo --module github.com/you/store-demo --storage-type file
45+
fabrica add resource Widget
46+
fabrica generate && go run ./cmd/server/ serve --data-dir ./data &
47+
go run ./cmd/client/ widget list --output json
48+
```
49+
50+
This is the same flow you would use for a database backend. Change the init flags to `--storage-type ent --db sqlite` and start the server with `--database-url "file:data/app.db?_fk=1"`. The SQLite foreign key pragma (`?_fk=1`) is required so relationships enforce correctly.
51+
52+
## What to watch for in production
53+
54+
Think about where your data lives and how you back it up. The file backend writes under a data directory (by default `./data`). Make sure that directory exists and is writable. If you run containers, mount a volume. If you change the path, keep it consistent across restarts.
55+
56+
For Ent, pin and run migrations outside the server for production. The init template sets up schema creation that is great for development, but real deployments benefit from controlled migration steps. Watch connection strings and options. For SQLite, include `?_fk=1` so foreign keys enforce. For Postgres and MySQL, make sure drivers are on your path and your URLs match your environment.
57+
58+
Events are optional. The in‑memory bus is perfect for local development, but a real system may want a durable bus. The event helpers live in `pkg/events/events.go`. You can swap the bus implementation without changing handlers. Reconciliation is optional too. It depends on events and the resource model, not on storage internals.
59+
60+
Concurrency and conflicts are handled above storage. Conditional requests use ETags so you can protect writes. That logic is generated into middleware and handlers, not buried in the storage adapter. The separation of layers keeps storage simple and the API stable.
61+
62+
## Related reading
63+
64+
If you want to see a database in action, read the FRU service example at `examples/03-fru-service/README.md`. It uses Ent with SQLite and shows a richer resource model for hardware inventory.
65+
66+
For a deeper dive on database storage, see `docs/guides/storage-ent.md`. If you are staying on files for now, the backend implementation in `pkg/codegen/templates/storage/file.go.tmpl` is short and worth a read. You can also scan `pkg/codegen/templates/server/handlers.go.tmpl` to see the Spec vs. Status split and how requests map to storage calls. Finally, `pkg/codegen/generator.go` ties the feature flags and resource metadata to the templates, and `cmd/fabrica/add.go` shows how new resources are wired into the code generator.
67+
68+
The bottom line: pick the backend that fits today. You can change it later with a config edit and a regeneration step. Your routes and clients don’t need to know the difference.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 OpenCHAMI Contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
---
8+
layout: post
9+
title: "Reconciliation: Let the System Do the Work"
10+
description: "A gentle look at controllers that react to change and keep resources in a good state."
11+
---
12+
13+
Some jobs are better done by the system than by users clicking through steps. Reconciliation makes this possible. You declare what you want in Spec, and a controller reads that intent and works until the resource reaches a steady state. When things drift, the controller nudges them back.
14+
15+
In Fabrica, reconciliation is a first‑class pattern. The generated server publishes events when resources change, and the reconciliation controller listens and enqueues work. Your code reacts by reading the resource, taking actions, and updating Status. See `pkg/events/events.go` for event helpers and `pkg/reconcile/controller.go` for the controller.
16+
17+
## What you get
18+
19+
You get a clean split between user intent and system state. Users set Spec. Controllers update Status. The handlers template (`pkg/codegen/templates/server/handlers.go.tmpl`) keeps those paths separate so your controller never fights with user writes.
20+
21+
You also get the plumbing: event publishing from handlers, an in‑memory event bus for local runs, a controller with a work queue, and hooks to register your reconcilers. The CLI wires features into generation (see `cmd/fabrica/add.go`), and the generator ties resource metadata to templates (see `pkg/codegen/generator.go`).
22+
23+
## How it works under the hood
24+
25+
When a resource is created or changed, handlers emit lifecycle events (see `pkg/events/events.go`). The reconciliation controller (`pkg/reconcile/controller.go`) subscribes and enqueues the affected resource. Your reconciler runs: it reads the resource from storage, checks Status, decides what to do, and writes back Status updates.
26+
27+
The handler template (`pkg/codegen/templates/server/handlers.go.tmpl`) includes a dedicated Status endpoint. That makes reconcilers safe and idempotent: they only write Status, while users write Spec. Storage backends (file or database) don’t matter here; reconcilers use the same storage client either way.
28+
29+
## Trade‑offs and limits
30+
31+
Reconciliation is eventual, not instant. Design reconcilers to be idempotent and safe to retry. Handle backoff and requeue. Expect out‑of‑order events. Don’t try to “fix” Spec from a controller—write to Status and let users update Spec.
32+
33+
The default event bus is in‑memory. It’s perfect for local development. For production, consider a durable bus. The controller uses a work queue; size and worker count set throughput and memory use.
34+
35+
## Try it
36+
37+
Here’s a tiny flow that turns on reconciliation and uses the generated client. This uses the file backend so it runs anywhere. If you add reconcilers (like in the rack example), you’ll see the controller act on new resources.
38+
39+
```bash
40+
fabrica init rack-svc --module github.com/you/rack-svc --storage-type file --events --reconcile
41+
fabrica add resource Rack
42+
fabrica generate && go run ./cmd/server/ serve --data-dir ./data &
43+
go run ./cmd/client/ rack create --spec '{"name":"rack-01"}'
44+
```
45+
46+
If you prefer a database, switch to Ent at init and start the server with `--database-url "file:data/app.db?_fk=1"`. The `?_fk=1` pragma enables SQLite foreign keys.
47+
48+
## What to watch for in production
49+
50+
Make reconcilers idempotent. Use conditions on Status to record progress and errors. Keep steps small so you can retry safely. Choose a durable event bus if you need persistence or fan‑out. Tune worker counts and queue sizes to match your load. For the server, remember the `go run ./cmd/server/` trailing slash and ensure your data dir exists if you use the file backend.
51+
52+
## Related reading
53+
54+
Read how controllers are structured in `pkg/reconcile/controller.go` and how events are published in `pkg/events/events.go`. See how handlers separate Spec and Status in `pkg/codegen/templates/server/handlers.go.tmpl`. The CLI wiring lives in `cmd/fabrica/add.go`, and the generator in `pkg/codegen/generator.go`.
55+
56+
For a concrete walkthrough, check `docs/guides/reconciliation.md` and the example at `examples/04-rack-reconciliation/`.

0 commit comments

Comments
 (0)