The generate model command

The generate model command generates a Protobuf message that will act as a "view model", being the representation of a record transferred over the wire to your gRPC network service, and a bucket definition for storage.

Model records must be identified by a string-based surrogate key, such as an XID or UUID, to be compatible with modern scale-out storage systems.

The generate model command is used like this:

$ saas-rs generate model invoice --service user --version 1 customer_id address_1 address_2 city state zip postal_code country_iso2
$ make
$ git add -A
$ git commit -m "saas-rs generate model invoice --service user --version 1 customer_id address_1 address_2 city state zip postal_code country_iso2" 

The following content is added or changed in your Rust workspace:

crates/
├── config_store/
│   ├── src/
│   │   └── bucket.rs
├── protocol/
│   ├── src/
│   │   └── generated/
│   │       ├── acme_user_v1.rs
│   │       └── acme_user_v1_serde.rs
proto/
└── acme/
    └── user/
        └── v1/
            ├── invoice.proto
            └── user_service.proto

An examination of the proto/acme/user/v1/invoice.proto file shows the generated view model:

syntax = "proto3";

package acme.user.v1;

import "google/protobuf/timestamp.proto";

message Invoice {
    string id = 1;
    string customer_id = 2;
    string address_1 = 3;
    string address_2 = 4;
    string city = 5;
    string state = 6;
    string zip = 7;
    string postal_code = 8;
    string country_iso2 = 9;
    
    google.protobuf.Timestamp created_at = 1000;
    optional string created_by_account_id = 1001;
    optional google.protobuf.Timestamp deleted_at = 1002;
    optional string deleted_by_account_id = 1003;
    optional google.protobuf.Timestamp updated_at = 1004;
    optional string updated_by_account_id = 1005;
    oneof owner {
        string owner_account_id = 1006;
    }
}

And for this new Protobuf file to be found by the Prost code generator, it needs to be referenced by the proto/acme/user/v1/user_sevice.proto file:

import "acme/user/v1/invoice.proto";

Also notice that a Bucket enum variant was added in crates/config_store/src/bucket.rs, which helps the storage layer understand that this bucket represents:

  • a new table, if you'll be using an RDBMS based ConfigStore
  • a new collection, if you'll be using a Document based ConfigStore
  • or new a key prefix, if you'll be using a KV based ConfigStore
pub enum Bucket {
    #[strum(serialize = "invoices")]
    Invoices,
    ...
}

Storage models

View models are translated into storage models before being used with a storage adapter. Storage models are general-purpose BSON documents instead of concrete structs, so that you don't have to redundantly define both view models and storge models. View models are converted to/from storage models with help from the pbbson crate. Storage models will be elaborated in another section.

Compound model names

It's perfectly valid to define a new model with a compound name, such as LinkedAccount or InvoiceChangeAction.

When the generate model command is used like this:

$ saas-rs generate model invoice-change-action --service user --version 1

The model filename is automatically snake-cased to:

proto/acme/user/v1/invoice_change_action.proto

And the bucket enum variant's strum serialize attribute defines a camel-cased + pluralized bucket name for KV and Document stores, while the enum variant identifier itself is pascal-cased + pluralized:

pub enum Bucket {
    #[strum(serialize = "invoiceChangeActions")]
    InvoiceChangeActions,
...

Making Further Changes

The fields initially generated are completely customizable. Only the id and audit fields are expected to remain unaltered. So there's no difference between invoking the generate model command with a complete field list, or invoking it with an empty list and typing in fields by hand.

Some common changes to models include:

  • setting fields as optional
  • tweaking the datatypes of the generated fields
  • adding additional audit fields, such as last_viewed_by and last_viewed_at, or approved_by and approved_at

©2025 SaaS RS | Website | GitHub | GitLab | Contact