this

๐ŸŽฏ Latest Changes

v0.0.7 - GraphQL API Exposure (Latest)

Summary

Major new feature introducing dynamic GraphQL API exposure with automatic schema generation from registered entities.

Date: January 2025
Version: 0.0.7
Impact: New feature - No breaking changes

New Features

Implementation

The GraphQL exposure is built with:

Usage

Enable GraphQL feature:

[dependencies]
this-rs = { version = "0.0.7", features = ["graphql"] }

Build server with GraphQL:

let host = Arc::new(
    ServerBuilder::new()
        .with_link_service(InMemoryLinkService::new())
        .register_module(module)?
        .build_host()?
);

let graphql_router = GraphQLExposure::build_router(host)?;

Documentation

Example Queries

List entities:

query {
  orders(limit: 10) {
    id
    number
    customerName
    amount
  }
}

Get with relations:

query {
  order(id: "UUID") {
    id
    number
    invoices {
      id
      amount
      payments {
        id
        method
      }
    }
  }
}

Create entity:

mutation {
  createOrder(data: {
    number: "ORD-001"
    customerName: "John Doe"
    amount: 1000.0
    status: "active"
  }) {
    id
    number
  }
}

Next Steps


v0.0.6 - Generic Pagination and Filtering

Summary

New feature introducing generic pagination and filtering for all list endpoints (entities and links), with automatic pagination by default to prevent returning all data at once.

Date: December 2024
Version: 0.0.6
Impact: Enhancement - Default pagination applied

New Features

Migration Guide

No breaking changes! Pagination is now automatic:

// Before - would return ALL entities
GET /orders

// After - automatically paginated
GET /orders
Response: { "data": [...], "pagination": { "page": 1, "limit": 20, ... } }

// You can still get all data with explicit pagination
GET /orders?limit=1000

Documentation

Example Usage

# List entities with default pagination (20 per page)
curl 'http://127.0.0.1:3000/orders'

# Custom pagination
curl 'http://127.0.0.1:3000/orders?page=2&limit=10'

# With filters
curl 'http://127.0.0.1:3000/orders?filter={"status":"pending"}'

# Links with pagination
curl 'http://127.0.0.1:3000/orders/{id}/invoices?page=1&limit=5'

# Links with filters on nested entities
curl 'http://127.0.0.1:3000/orders/{id}/invoices?filter={"target.status":"paid"}'

# Nested links (3+ levels) with pagination
curl 'http://127.0.0.1:3000/orders/{id}/invoices/{id}/payments?page=1&limit=10'

v0.0.5 - Automatic Validation and Filtering

Summary

Major new feature introducing automatic validation and filtering with declarative rules defined directly in entity models.

Date: December 2024
Version: 0.0.5
Impact: New feature - No breaking changes

New Features

Migration Guide

If youโ€™re using impl_data_entity!, you can continue using it. However, we recommend upgrading to impl_data_entity_validated!:

// Old way (still works)
impl_data_entity!(Invoice, "invoice", ["number"], { amount: f64 });

// New way with validation
impl_data_entity_validated!(
    Invoice, "invoice", ["number"], { amount: f64 },
    validate: {
        create: {
            amount: [required positive max_value(1_000_000.0)],
        },
    },
    filters: {
        create: {
            amount: [round_decimals(2)],
        },
    }
);

Documentation


v0.0.2 - Entity System Refactoring

Summary

Major architectural refactoring introducing a macro-driven entity system, automatic entity creation with linking, and removal of multi-tenancy in favor of a cleaner, simpler architecture.

Date: October 2024
Version: 0.0.2
Impact: BREAKING CHANGES - Migration required


๐Ÿ”ฅ Major Changes

1. โŒ Removed Multi-Tenancy

Before (v0.0.1):

struct Order {
    id: Uuid,
    tenant_id: Uuid,  // โŒ Required everywhere
    name: String,
    amount: f64,
}

// All service methods required tenant_id
service.create(&tenant_id, order).await?;
service.get(&tenant_id, &id).await?;

After (v0.0.2):

// โœ… No tenant_id!
impl_data_entity!(Order, "order", ["name"], {
    amount: f64,
});

// Clean API without tenant_id
service.create(order).await?;
service.get(&id).await?;

Rationale:

2. โœ… New Entity Hierarchy

3-Level Architecture:

Entity (Base)
โ”œโ”€โ”€ id: Uuid
โ”œโ”€โ”€ type: String
โ”œโ”€โ”€ created_at: DateTime<Utc>
โ”œโ”€โ”€ updated_at: DateTime<Utc>
โ”œโ”€โ”€ deleted_at: Option<DateTime<Utc>>  โœ… Built-in soft delete
โ””โ”€โ”€ status: String

    โ”œโ”€โ–บ Data (Business entities)
    โ”‚   โ””โ”€โ”€ name: String
    โ”‚       + custom fields...
    โ”‚
    โ””โ”€โ–บ Link (Relationships)
        โ”œโ”€โ”€ source_id: Uuid
        โ”œโ”€โ”€ target_id: Uuid
        โ””โ”€โ”€ link_type: String

Benefits:

3. โœ… Macro-Driven Entity Definitions

Before (v0.0.1): Manual boilerplate (50+ lines per entity)

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Order {
    id: Uuid,
    tenant_id: Uuid,
    name: String,
    amount: f64,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
    // ... 10+ more lines of fields
}

impl Entity for Order {
    // ... 30+ lines of trait implementation
}

impl Data for Order {
    // ... 20+ lines of trait implementation
}

After (v0.0.2): Macro magic! (4 lines)

impl_data_entity!(Order, "order", ["name", "amount"], {
    amount: f64,
});

// โœจ Auto-generates:
// - All base Entity fields (id, type, created_at, etc.)
// - name field (from Data trait)
// - Your custom fields
// - Full trait implementations (Entity, Data)
// - Constructor: Order::new(...)
// - Utility methods: soft_delete(), touch(), set_status(), restore()
// - Serde implementations

Macros Provided:

4. โœ… EntityCreator Trait for Auto-Creation

New Feature: Create entities automatically when creating links!

// Implement EntityCreator on your store
#[async_trait]
impl EntityCreator for OrderStore {
    async fn create_from_json(&self, entity_data: serde_json::Value) -> Result<serde_json::Value> {
        let order = Order::new(
            entity_data["name"].as_str().unwrap_or("").to_string(),
            entity_data["status"].as_str().unwrap_or("active").to_string(),
            entity_data["amount"].as_f64().unwrap_or(0.0),
        );
        self.add(order.clone());
        Ok(serde_json::to_value(order)?)
    }
}

Usage: Two ways to create links now!

POST /orders/{order_id}/invoices/{invoice_id}
Body: { "metadata": { "note": "Standard invoice" } }
POST /orders/{order_id}/invoices
Body: {
  "entity": {
    "name": "INV-999",
    "amount": 999.99,
    "status": "active"
  },
  "metadata": { "note": "Auto-created" }
}

# Returns BOTH entity and link!
{
  "entity": { "id": "...", "name": "INV-999", ... },
  "link": { "id": "...", "source_id": "...", "target_id": "...", ... }
}

Feature: Links now include full entity data automatically!

Before (v0.0.1):

{
  "links": [
    {
      "id": "link-123",
      "source_id": "order-abc",
      "target_id": "invoice-xyz"
      // โŒ Need separate query to get invoice data
    }
  ]
}

After (v0.0.2):

{
  "links": [
    {
      "id": "link-123",
      "source_id": "order-abc",
      "target_id": "invoice-xyz",
      "target": {
        "id": "invoice-xyz",
        "type": "invoice",
        "name": "INV-001",
        "amount": 1500.00,
        // โœ… Full entity data included!
        ...
      }
    }
  ]
}

Smart Enrichment:

6. โœ… Updated Module Trait

New Methods:

pub trait Module: Send + Sync {
    // ... existing methods ...
    
    // NEW: Provide entity fetchers for link enrichment
    fn get_entity_fetcher(&self, entity_type: &str) -> Option<Arc<dyn EntityFetcher>>;
    
    // NEW: Provide entity creators for auto-creation
    fn get_entity_creator(&self, entity_type: &str) -> Option<Arc<dyn EntityCreator>>;
}

๐Ÿ“Š Impact Comparison

Metric Before (v0.0.1) After (v0.0.2) Improvement
Lines per entity ~150 ~4 -97%
Manual routing Required Auto-generated 100%
tenant_id everywhere Yes โŒ No โœ… Cleaner API
Link creation methods 1 2 More flexible
Link enrichment Manual Automatic Better DX
Soft delete Manual Built-in Standardized

๐Ÿ”„ Migration Guide

Step 1: Remove tenant_id

Before:

struct Order {
    id: Uuid,
    tenant_id: Uuid,  // โŒ Remove
    name: String,
}

service.create(&tenant_id, order).await?;

After:

impl_data_entity!(Order, "order", ["name"], {
    amount: f64,
});

service.create(order).await?;  // โœ… No tenant_id

Step 2: Use Macros for Entity Definitions

Before: 150+ lines of boilerplate

After: 4 lines with macro

impl_data_entity!(Order, "order", ["name", "number"], {
    number: String,
    amount: f64,
    customer_name: Option<String>,
});
#[async_trait]
impl EntityCreator for OrderStore {
    async fn create_from_json(&self, entity_data: serde_json::Value) -> Result<serde_json::Value> {
        let order = Order::new(
            entity_data["name"].as_str().unwrap_or("").to_string(),
            entity_data["status"].as_str().unwrap_or("active").to_string(),
            entity_data["number"].as_str().unwrap_or("").to_string(),
            entity_data["amount"].as_f64().unwrap_or(0.0),
            entity_data["customer_name"].as_str().map(String::from),
        );
        self.add(order.clone());
        Ok(serde_json::to_value(order)?)
    }
}

Step 4: Update Module Implementation

impl Module for BillingModule {
    // ... existing methods ...
    
    fn get_entity_fetcher(&self, entity_type: &str) -> Option<Arc<dyn EntityFetcher>> {
        match entity_type {
            "order" => Some(Arc::new(self.store.orders.clone())),
            _ => None,
        }
    }
    
    fn get_entity_creator(&self, entity_type: &str) -> Option<Arc<dyn EntityCreator>> {
        match entity_type {
            "order" => Some(Arc::new(self.store.orders.clone())),
            _ => None,
        }
    }
}

Step 5: Update API Calls

Link Creation - Before:

# Only one way
POST /orders/{id}/invoices
Body: { "target_id": "{invoice_id}", "metadata": {...} }

Link Creation - After:

# Method 1: Link existing
POST /orders/{order_id}/invoices/{invoice_id}
Body: { "metadata": {...} }

# Method 2: Create new + link
POST /orders/{order_id}/invoices
Body: { "entity": {...}, "metadata": {...} }

โœจ New Features

1. Built-in Soft Delete

let mut order = Order::new(...);
order.soft_delete();  // Sets deleted_at timestamp
// Order still in database, just marked as deleted

order.restore();  // Clears deleted_at
// Order is active again

2. Automatic Timestamp Management

let mut order = Order::new(...);
// created_at and updated_at are auto-set

order.touch();  // Updates updated_at to now

3. Status Management

let mut order = Order::new(...);
order.set_status("completed".to_string());
// Status changed + updated_at refreshed

4. Type-Safe Entity Types

impl_data_entity!(Order, "order", ["name"], { amount: f64 });
// entity.entity_type() always returns "order"
// Impossible to have type mismatches!

๐ŸŽฏ Benefits

For Developers

โœ… 97% less boilerplate - Macros generate everything
โœ… Cleaner API - No tenant_id clutter
โœ… More flexibility - Two ways to create links
โœ… Better DX - Auto-enriched link responses
โœ… Built-in features - Soft delete, timestamps, status

For Teams

โœ… Faster development - Less code to write
โœ… Easier maintenance - Less code to maintain
โœ… Consistent patterns - Macros enforce consistency
โœ… Better onboarding - Simpler concepts

For Production

โœ… Performance - Efficient link enrichment
โœ… Flexibility - Choose tenancy model
โœ… Reliability - Type-safe, compile-time checks
โœ… Scalability - Clean architecture scales well


๐Ÿ“š Updated Documentation

All documentation has been updated to reflect these changes:


๐Ÿ”ฎ Future Enhancements

Planned for v0.0.3+:


๐Ÿค Breaking Changes Summary

Change Migration Effort Impact
Removed tenant_id Medium All entities and service calls
New entity hierarchy Low Only trait implementations
Macro system Low Simplifies entity definition
EntityCreator trait Low Optional, add when needed
Module trait methods Low Just add two new methods

๐ŸŽ‰ Conclusion

This refactoring makes this-rs:

The framework is now production-ready with a clean, modern architecture! ๐Ÿš€๐Ÿฆ€โœจ


๐Ÿ“ž Support

Questions or issues with migration?

Welcome to this-rs v0.0.2! ๐ŸŽŠ