Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Shared Protocol Buffer definitions and custom options for TrogonStack projects.**

**trogon-proto provides reusable protobuf extensions that standardize common patterns across services.** It includes custom options for UUID generation, identity versioning, and other cross-cutting concerns.
**trogon-proto provides reusable protobuf extensions and value types that standardize common patterns across services.** It includes custom options for UUID generation, domain actor identity, identity versioning, and other cross-cutting concerns.

**Protocol Buffer options let you attach metadata to your definitions that can be read at runtime or during code generation.** This eliminates documentation drift and ensures all services follow the same conventions for critical patterns like deterministic ID generation.

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Document Errors in Proto](how-to/document-errors-in-proto.md)
- [Document NATS Micro Services](how-to/document-nats-micro-services.md)
- [Actor Identity](explanation/actor-identity.md)
- [Google RPC Error Templates](explanation/google-rpc-error-template.md)
- [Protobuf Extension Naming](explanation/protobuf-extension-naming.md)
- [Consistency Pattern](explanation/consistency-pattern.md)
94 changes: 94 additions & 0 deletions docs/explanation/actor-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Actor Identity

The `trogon.actor.v1alpha1.Actor` message records who caused a domain event or action when consumers need both an actor type and an actor id.

At application boundaries, the caller may be represented as an authenticated principal, service account, workload identity, or system process. Domain events should not need to expose that IAM vocabulary directly. They need a stable actor value that preserves causality.

## Choosing Actor or ActorId

Prefer `Actor` for shared event metadata and any field where consumers may need to distinguish users, services, systems, schedulers, integrations, or other actor classes. This is the safer default for reusable event envelopes because the actor taxonomy remains part of the field contract.

Use `ActorId` only when the surrounding event contract already fixes the actor class and the string identifier alone is the durable domain fact. Do not use `ActorId` just to avoid the `type` field in JSON.

For example, common event metadata should usually use `Actor`:

```protobuf
message EventMetadata {
trogon.actor.v1alpha1.Actor actor = 1;
}
```

The protobuf JSON shape is object-based:

```json
{
"actor": {
"type": "user",
"id": "usr_123"
}
}
```

`type` is an open, stable token instead of an enum because actor taxonomies vary across services and may evolve independently. Use generic values such as `user`, `service`, `system`, `scheduler`, or `integration` when they are precise enough. Use namespaced values when a service owns a more specific actor class.

`id` is an opaque identifier scoped by `type`. It is not display text, and consumers should compare actors by both fields.

An event-specific field can use `ActorId` when the domain fact is only the identifier and the actor class is already implied:

```protobuf
message AccountCreated {
trogon.actor.v1alpha1.ActorId created_by = 1;
}
```

The protobuf JSON shape for `ActorId` is:

```json
{
"createdBy": {
"value": "usr_123"
}
}
```

Changing a field from `ActorId` to `Actor` later changes the protobuf and JSON shape for that field. Choose `Actor` up front when a later type distinction would belong to the same event contract.

## Object ID Values

An actor id may use the same string format as an object id, but the field type should follow the domain role, not the string format.

Use `Actor` or `ActorId` when the field answers who caused the event:

```protobuf
message AccountCreated {
trogon.actor.v1alpha1.Actor created_by = 1;
}
```

```json
{
"createdBy": {
"type": "user",
"id": "usr_123"
}
}
```

Do not use `ActorId` for fields that identify the object being created, updated, or referenced. Those fields should use the domain object's own id type or string field:

```protobuf
message AccountCreated {
string account_id = 1;
trogon.actor.v1alpha1.Actor created_by = 2;
}
```

If the event contract already implies the actor type, an object-id string can be carried by `ActorId`:

```protobuf
message UserProfileUpdated {
trogon.actor.v1alpha1.ActorId updated_by = 1;
}
```

In that case, the value is still an actor identifier in the event, not a general object reference.
39 changes: 39 additions & 0 deletions proto/trogon/actor/v1alpha1/actor.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
syntax = "proto3";
package trogon.actor.v1alpha1;

import "elixirpb.proto";

option (elixirpb.file).module_prefix = "TrogonProto.Actor.V1Alpha1";

// ActorId identifies a domain actor when the actor type is already implied by
// the surrounding contract.
//
// Use Actor when event consumers need both type and id. Use ActorId only when
// the identifier alone is the durable domain contract.
message ActorId {
// value is a stable opaque actor identifier.
//
// This value is not display text and is comparable only within the domain
// boundary defined by the surrounding contract.
string value = 1;
}

// Actor identifies the domain entity that caused an event or action.
//
// Use this message in event metadata or event payload fields such as
// created_by, approved_by, or cancelled_by. The application layer maps
// authenticated principals or system processes into this domain-facing actor.
message Actor {
// type is a stable non-empty token that classifies the actor identifier.
//
// Examples: "user", "service", "system", "scheduler", "integration".
// Service-owned values should be namespaced when generic tokens are not
// precise enough.
string type = 1;

// id is a stable opaque identifier scoped by type.
//
// This value is not display text and should not be assumed to be globally
// comparable without the accompanying type.
string id = 2;
}
Loading