Skip to content

Behavioral Traits

lpachecob edited this page Mar 23, 2026 · 6 revisions

🛡️ Behavioral Traits: Pro Attributes

In BOUNDLY, you add enterprise features with a single class attribute. These traits automate horizontal concerns like auditing, deletion, segmentation, and authorization.


🛡️ #[Auditable]

Automates traceability for creation and modifications.

Column Added Source
created_by X-User-ID request header (or 'System')
updated_by X-User-ID request header (or 'System')

How it works:

  1. The DB: core:migrate adds created_by and updated_by as VARCHAR columns.
  2. The Logic: On every insert() or update(), BOUNDLY auto-populates these from the X-User-ID request header.
#[Entity(table: 'products', resource: 'products')]
#[Auditable]
class Product extends AggregateRoot { ... }

🗑️ #[SoftDelete]

Enables logical deletion — the row is never physically removed.

Column Added Behavior
deleted_at Set to now() on DELETE. NULL means active.

How it works:

  1. The DB: core:migrate adds deleted_at as a TIMESTAMP.
  2. The Queries: All GET requests automatically filter WHERE deleted_at IS NULL.
  3. The Action: DELETE sets the timestamp instead of executing a DELETE SQL command.
#[Entity(table: 'orders', resource: 'orders')]
#[SoftDelete]
class Order extends AggregateRoot { ... }

🏢 #[TenantAware]

Isolates data by Tenant ID. Essential for SaaS applications.

Parameter Default Description
tenantColumn 'tenant_id' The DB column holding the tenant identifier.

How it works:

  1. The DB: core:migrate adds the tenant column.
  2. The Queries: Every SELECT, INSERT, UPDATE, and DELETE scopes automatically to WHERE tenant_id = <X-Tenant-ID header>.
  3. Zero configuration: Pass the X-Tenant-ID header in every API request.
#[Entity(table: 'inventories', resource: 'inventories')]
#[TenantAware(tenantColumn: 'tenant_id')]
class Inventory extends AggregateRoot { ... }

🔐 #[Authorize]

Protects an entity's API routes with authentication and role-based access control (RBAC). The programmer never touches route files or middleware registration.

Parameter Default Description
roles [] Array of required role names. Empty = any authenticated user.
methods [] HTTP methods this rule applies to. Empty = all methods.
guard 'sanctum' Laravel auth guard to use.

How it works:

  1. The ResourceAuthorize middleware reads the #[Authorize] attribute automatically before every request.
  2. If roles is empty: any authenticated user is allowed.
  3. If roles is set: compatible with Spatie Laravel Permission (hasAnyRole) and a simple role column.
  4. The methods parameter allows fine-grained control (e.g., allow public reads but require auth for writes).
// Any authenticated user can access this resource
#[Entity(table: 'reports', resource: 'reports')]
#[Authorize]
class Report extends AggregateRoot { ... }

// Only admins can access this resource
#[Entity(table: 'salaries', resource: 'salaries')]
#[Authorize(roles: ['admin'])]
class Salary extends AggregateRoot { ... }

// Public reads, authenticated writes
#[Entity(table: 'articles', resource: 'articles')]
#[Authorize(roles: [], methods: ['POST', 'PUT', 'PATCH', 'DELETE'])]
class Article extends AggregateRoot { ... }

💡 #[Authorize] is repeatable — you can stack multiple rules on the same class.


🧪 Combinations

Combine all traits for a truly enterprise-level entity:

#[Entity(table: 'clients', resource: 'clients')]
#[Auditable]
#[SoftDelete]
#[TenantAware]
#[Authorize(roles: ['admin', 'manager'])]
class Client extends AggregateRoot { ... }

With this single declaration, the clients resource:

  • ✅ Auto-creates and evolves its table
  • ✅ Tracks who created and modified each record
  • ✅ Uses logical deletion
  • ✅ Is isolated per tenant
  • ✅ Is only accessible to admin and manager roles

Next Step: Query-Engine 🔎

Clone this wiki locally