Project

facera

0.0
The project is in a healthy, maintained state
Facera allows you to define your system once as a semantic core and expose it through multiple facets, each tailored to different consumers while remaining logically consistent.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 2.0
 Project Readme

Facera

One semantic core. Multiple API facets. Zero duplication.

Facera is a Ruby framework for building multi-facet APIs from a single semantic core. Define your domain model once, then expose different views for different consumers—all guaranteed consistent by design.

Facera logo

Gem Version Ruby CI License


Why Facera?

Modern systems expose APIs to many different consumers, leading to duplicated endpoints, inconsistent representations, and maintenance burden. Facera solves this by letting you define your system once as a semantic core, then automatically generate multiple consistent API facets.


Quick Start

Installation

gem install facera

Or add to your Gemfile:

gem 'facera'

1. Define Your Core

# cores/payment_core.rb
Facera.define_core(:payment) do
  entity :payment do
    attribute :id, :uuid, immutable: true
    attribute :amount, :money, required: true
    attribute :currency, :string, required: true
    attribute :status, :enum, values: [:pending, :confirmed, :cancelled]
    attribute :merchant_id, :uuid, required: true
    attribute :customer_id, :uuid, required: true
  end

  capability :create_payment, type: :create do
    entity :payment
    requires :amount, :currency, :merchant_id, :customer_id
    validates { amount > 0 }
  end

  capability :confirm_payment, type: :action do
    entity :payment
    requires :id
    precondition { status == :pending }
    transitions_to :confirmed
  end
end

2. Define Facets

All audience facets for a core live in a single file named after the core:

# facets/payment_facets.rb
# Facet name = audience name only. The core is declared via core:.
# Same audience name across cores → grouped into one API at /{audience}/{version}.

Facera.define_facet(:public, core: :payment) do
  description "Customer-facing payments API"

  expose :payment do
    fields :id, :amount, :currency, :status
  end

  allow_capabilities :create_payment, :get_payment
  error_verbosity :minimal
  rate_limit requests: 1000, per: :hour
end

Facera.define_facet(:internal, core: :payment) do
  description "Internal service-to-service payments API"

  expose :payment do
    fields :all
    computed :age_in_seconds do
      created_at ? (Time.now - created_at).to_i : 0
    end
  end

  allow_capabilities :all
  error_verbosity :detailed
end

Facera.define_facet(:ops, core: :payment) do
  description "Operator and support agent payments API"

  expose :payment do
    fields :all
    computed :customer_display do
      "Customer #{customer_id[0..7]}"
    end
  end

  allow_capabilities :all
  error_verbosity :detailed
  audit_all_operations user: :current_agent
end

Name facets after the business audience consuming them. Facets sharing the same audience name across cores are automatically merged into one API — no configuration required.

3. Implement Business Logic

Create an adapter to implement the actual logic:

# adapters/payment_adapter.rb
class PaymentAdapter
  include Facera::Adapter

  def create_payment(params)
    # Your business logic here
    Payment.create!(
      amount: params[:amount],
      currency: params[:currency],
      merchant_id: params[:merchant_id],
      customer_id: params[:customer_id],
      status: :pending
    )
  end

  def get_payment(params)
    Payment.find(params[:id])
  end

  def confirm_payment(params)
    payment = Payment.find(params[:id])
    payment.update!(status: :confirmed, confirmed_at: Time.now)
    payment
  end
end

4. Mount and Run

# config.ru
require 'facera'

app = Rack::Builder.new do
  Facera.auto_mount!(self)
end

run app

Start your server:

rackup -p 9292

That's it! Facera auto-discovers everything and generates:

# Public API (customer-facing) — /payments and /refunds
curl http://localhost:9292/public/api/v1/payments
curl -X POST http://localhost:9292/public/api/v1/payments \
  -H 'Content-Type: application/json' \
  -d '{"amount": 100.0, "currency": "USD", ...}'

# Internal API (service-to-service) — /payments and /refunds
curl http://localhost:9292/internal/api/v1/payments/:id

# Ops API (operators and support agents) — /payments and /refunds
curl http://localhost:9292/ops/api/v1/payments/:id

# Introspection
curl http://localhost:9292/facera/introspect
curl http://localhost:9292/facera/openapi/public

File Structure Convention

The key insight: one facets file per core, containing all audience variants.

your_app/
├── cores/
│   ├── payment_core.rb           # Payment domain model
│   └── refund_core.rb            # Refund domain model
├── adapters/
│   ├── payment_adapter.rb        # Payment business logic
│   └── refund_adapter.rb         # Refund business logic
├── facets/
│   ├── payment_facets.rb         # public, internal, ops  (core: :payment)
│   └── refund_facets.rb          # public, internal, ops   (core: :refund)
└── config.ru

Facera auto-discovers everything by convention — no manual registration needed.


Default Path Convention

Facet paths are derived automatically from the audience name. Facets sharing the same audience name across cores are grouped into one mounted API:

Audience name Cores included Default path Resources
public payment + refund /public/api/v1 /payments, /refunds
internal payment + refund /internal/api/v1 /payments, /refunds
ops payment + refund /ops/api/v1 /payments, /refunds

You can override any audience path explicitly if needed:

Facera.configure do |config|
  config.facet_path :public, '/public/api/v2'  # custom override
end

Key Features

  • 💎 Single Source of Truth - Define domain model once
  • 🎭 Multiple Facets - Different views for different consumers
  • 🚀 Auto-Generated APIs - REST endpoints created automatically
  • 📦 Zero Configuration - Convention-based auto-discovery
  • 📚 Built-in Introspection - Explore your APIs at runtime
  • 📄 OpenAPI Generation - Auto-generated API documentation
  • 🔒 Type Safety - Strong typing for entities and capabilities
  • Business Rules - Invariants and validations in the core

Documentation

Comprehensive documentation available in the wiki:


Examples

See the examples/ directory for complete working examples:

  • Phase Examples - 01_core_dsl.rb through 04_auto_mounting.rb
  • Runnable Server - examples/server/ with multiple cores and facets

Run the server:

cd examples/server
rackup -p 9292

Contributing

Contributions welcome! Please read CONTRIBUTING.md for guidelines.

git clone https://github.com/yourusername/facera.git
cd facera
bundle install
bundle exec rspec

License

MIT License - see LICENSE for details


Credits

Created by Juan Carlos Garcia


One semantic core. Multiple API facets. Zero duplication.

Start building better APIs today! 💎