facera 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.github/workflows/gem-push.yml +42 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +39 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +137 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +309 -0
- data/Rakefile +6 -0
- data/examples/01_core_dsl.rb +132 -0
- data/examples/02_facet_system.rb +216 -0
- data/examples/03_api_generation.rb +117 -0
- data/examples/04_auto_mounting.rb +182 -0
- data/examples/05_adapters.rb +196 -0
- data/examples/README.md +184 -0
- data/examples/server/README.md +376 -0
- data/examples/server/adapters/payment_adapter.rb +139 -0
- data/examples/server/application.rb +17 -0
- data/examples/server/config/facera.rb +33 -0
- data/examples/server/config.ru +10 -0
- data/examples/server/cores/payment_core.rb +82 -0
- data/examples/server/facets/external_facet.rb +38 -0
- data/examples/server/facets/internal_facet.rb +33 -0
- data/examples/server/facets/operator_facet.rb +48 -0
- data/facera.gemspec +30 -0
- data/img/facera.png +0 -0
- data/lib/facera/adapter.rb +83 -0
- data/lib/facera/attribute.rb +95 -0
- data/lib/facera/auto_mount.rb +124 -0
- data/lib/facera/capability.rb +117 -0
- data/lib/facera/capability_access.rb +59 -0
- data/lib/facera/configuration.rb +83 -0
- data/lib/facera/context.rb +29 -0
- data/lib/facera/core.rb +65 -0
- data/lib/facera/dsl.rb +41 -0
- data/lib/facera/entity.rb +50 -0
- data/lib/facera/error_formatter.rb +100 -0
- data/lib/facera/errors.rb +40 -0
- data/lib/facera/executor.rb +265 -0
- data/lib/facera/facet.rb +103 -0
- data/lib/facera/field_visibility.rb +69 -0
- data/lib/facera/generators/core_generator.rb +23 -0
- data/lib/facera/generators/facet_generator.rb +25 -0
- data/lib/facera/generators/install_generator.rb +64 -0
- data/lib/facera/generators/templates/core.rb.tt +49 -0
- data/lib/facera/generators/templates/facet.rb.tt +23 -0
- data/lib/facera/grape/api_generator.rb +59 -0
- data/lib/facera/grape/endpoint_generator.rb +316 -0
- data/lib/facera/grape/entity_generator.rb +89 -0
- data/lib/facera/grape.rb +14 -0
- data/lib/facera/introspection.rb +111 -0
- data/lib/facera/introspection_api.rb +66 -0
- data/lib/facera/invariant.rb +26 -0
- data/lib/facera/loader.rb +153 -0
- data/lib/facera/openapi_generator.rb +338 -0
- data/lib/facera/railtie.rb +51 -0
- data/lib/facera/registry.rb +34 -0
- data/lib/facera/tasks/routes.rake +66 -0
- data/lib/facera/version.rb +3 -0
- data/lib/facera.rb +35 -0
- metadata +137 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require_relative '../lib/facera'
|
|
3
|
+
|
|
4
|
+
puts "=" * 80
|
|
5
|
+
puts "Example 05: Adapters & Business Logic Implementation"
|
|
6
|
+
puts "=" * 80
|
|
7
|
+
|
|
8
|
+
# Example 1: Inline execution blocks (for simple logic)
|
|
9
|
+
puts "\nš Example 1: Inline execution blocks"
|
|
10
|
+
puts "-" * 80
|
|
11
|
+
|
|
12
|
+
Facera.define_core(:simple_counter) do
|
|
13
|
+
entity :counter do
|
|
14
|
+
attribute :id, :uuid
|
|
15
|
+
attribute :count, :integer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
capability :increment_counter, type: :action do
|
|
19
|
+
entity :counter
|
|
20
|
+
requires :id
|
|
21
|
+
|
|
22
|
+
# Simple inline implementation
|
|
23
|
+
execute do |params|
|
|
24
|
+
{
|
|
25
|
+
id: params[:id],
|
|
26
|
+
count: 42, # In reality, fetch and increment
|
|
27
|
+
incremented_at: Time.now
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
counter_core = Facera::Registry.cores[:simple_counter]
|
|
34
|
+
puts "ā Core defined: #{counter_core.name}"
|
|
35
|
+
puts "ā Capability with execute block: #{counter_core.capabilities.keys.first}"
|
|
36
|
+
|
|
37
|
+
# Example 2: Adapter pattern (for complex logic)
|
|
38
|
+
puts "\nš Example 2: Adapter pattern"
|
|
39
|
+
puts "-" * 80
|
|
40
|
+
|
|
41
|
+
# Define the core
|
|
42
|
+
Facera.define_core(:blog) do
|
|
43
|
+
entity :post do
|
|
44
|
+
attribute :id, :uuid
|
|
45
|
+
attribute :title, :string, required: true
|
|
46
|
+
attribute :content, :string
|
|
47
|
+
attribute :status, :enum, values: [:draft, :published]
|
|
48
|
+
attribute :published_at, :datetime
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
capability :create_post, type: :create do
|
|
52
|
+
entity :post
|
|
53
|
+
requires :title, :content
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
capability :publish_post, type: :action do
|
|
57
|
+
entity :post
|
|
58
|
+
requires :id
|
|
59
|
+
precondition { status == :draft }
|
|
60
|
+
transitions_to :published
|
|
61
|
+
sets published_at: -> { Time.now }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Implement the adapter
|
|
66
|
+
class BlogAdapter
|
|
67
|
+
include Facera::Adapter
|
|
68
|
+
|
|
69
|
+
@@posts = {}
|
|
70
|
+
|
|
71
|
+
def create_post(params)
|
|
72
|
+
post = {
|
|
73
|
+
id: SecureRandom.uuid,
|
|
74
|
+
title: params[:title],
|
|
75
|
+
content: params[:content],
|
|
76
|
+
status: :draft,
|
|
77
|
+
created_at: Time.now
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@@posts[post[:id]] = post
|
|
81
|
+
|
|
82
|
+
puts " š¾ Created post: #{post[:title]}"
|
|
83
|
+
post
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def get_post(params)
|
|
87
|
+
@@posts[params[:id]]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def publish_post(params)
|
|
91
|
+
post = get_post(params)
|
|
92
|
+
|
|
93
|
+
# Do the actual publishing
|
|
94
|
+
post[:status] = :published
|
|
95
|
+
post[:published_at] = Time.now
|
|
96
|
+
|
|
97
|
+
# In production, you might:
|
|
98
|
+
# - Clear cache
|
|
99
|
+
# - Update search index
|
|
100
|
+
# - Send notifications
|
|
101
|
+
# - Trigger webhooks
|
|
102
|
+
|
|
103
|
+
puts " š¢ Published post: #{post[:title]}"
|
|
104
|
+
post
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Register the adapter
|
|
109
|
+
Facera::AdapterRegistry.register(:blog, BlogAdapter)
|
|
110
|
+
|
|
111
|
+
blog_core = Facera::Registry.cores[:blog]
|
|
112
|
+
puts "ā Core defined: #{blog_core.name}"
|
|
113
|
+
puts "ā Adapter registered: BlogAdapter"
|
|
114
|
+
|
|
115
|
+
# Example 3: Using both approaches
|
|
116
|
+
puts "\nš Example 3: Mixed approach (adapter + inline blocks)"
|
|
117
|
+
puts "-" * 80
|
|
118
|
+
|
|
119
|
+
Facera.define_core(:product) do
|
|
120
|
+
entity :product do
|
|
121
|
+
attribute :id, :uuid
|
|
122
|
+
attribute :name, :string
|
|
123
|
+
attribute :price, :money
|
|
124
|
+
attribute :stock, :integer
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Complex operation - use adapter
|
|
128
|
+
capability :create_product, type: :create do
|
|
129
|
+
entity :product
|
|
130
|
+
requires :name, :price
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Simple operation - use inline block
|
|
134
|
+
capability :check_stock, type: :action do
|
|
135
|
+
entity :product
|
|
136
|
+
requires :id
|
|
137
|
+
|
|
138
|
+
execute do |params|
|
|
139
|
+
# Simple stock check
|
|
140
|
+
{
|
|
141
|
+
id: params[:id],
|
|
142
|
+
stock: 10,
|
|
143
|
+
available: true
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class ProductAdapter
|
|
150
|
+
include Facera::Adapter
|
|
151
|
+
|
|
152
|
+
def create_product(params)
|
|
153
|
+
{
|
|
154
|
+
id: SecureRandom.uuid,
|
|
155
|
+
name: params[:name],
|
|
156
|
+
price: params[:price],
|
|
157
|
+
stock: 0,
|
|
158
|
+
created_at: Time.now
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
Facera::AdapterRegistry.register(:product, ProductAdapter)
|
|
164
|
+
|
|
165
|
+
product_core = Facera::Registry.cores[:product]
|
|
166
|
+
puts "ā Core defined: #{product_core.name}"
|
|
167
|
+
puts " - create_product: uses ProductAdapter"
|
|
168
|
+
puts " - check_stock: uses inline execute block"
|
|
169
|
+
|
|
170
|
+
# Summary
|
|
171
|
+
puts "\n" + "=" * 80
|
|
172
|
+
puts "⨠Summary"
|
|
173
|
+
puts "=" * 80
|
|
174
|
+
puts "
|
|
175
|
+
Facera supports two implementation patterns:
|
|
176
|
+
|
|
177
|
+
1. š Inline Blocks - For simple, one-off logic
|
|
178
|
+
capability :action do
|
|
179
|
+
execute do |params|
|
|
180
|
+
# Simple logic here
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
2. š Adapter Pattern - For complex, testable business logic
|
|
185
|
+
class MyAdapter
|
|
186
|
+
include Facera::Adapter
|
|
187
|
+
|
|
188
|
+
def capability_name(params)
|
|
189
|
+
# Complex logic here
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
Choose the right tool for the job!
|
|
194
|
+
"
|
|
195
|
+
|
|
196
|
+
puts "=" * 80
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Facera Examples
|
|
2
|
+
|
|
3
|
+
This directory contains examples demonstrating Facera's capabilities, organized by implementation phase.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Each example builds on the previous one, showing the progressive development of a multi-facet payment API:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
01_core_dsl.rb ā Phase 1: Core semantic definition
|
|
11
|
+
02_facet_system.rb ā Phase 2: Multiple facets from one core
|
|
12
|
+
03_api_generation.rb ā Phase 3: Auto-generated REST APIs
|
|
13
|
+
04_auto_mounting.rb ā Phase 4: Auto-mounting system
|
|
14
|
+
server/ ā Runnable HTTP server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Running the Examples
|
|
18
|
+
|
|
19
|
+
### Phase 1: Core DSL
|
|
20
|
+
|
|
21
|
+
Demonstrates the basic building blocks: entities, capabilities, and invariants.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ruby 01_core_dsl.rb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Shows:**
|
|
28
|
+
- Entity definition with typed attributes
|
|
29
|
+
- Capability definitions (create, get, list, actions)
|
|
30
|
+
- Business invariants
|
|
31
|
+
- Parameter requirements and validations
|
|
32
|
+
|
|
33
|
+
### Phase 2: Facet System
|
|
34
|
+
|
|
35
|
+
Shows how multiple facets can be created from a single core, each with different visibility and access rules.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ruby 02_facet_system.rb
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Shows:**
|
|
42
|
+
- 4 different facets (external, internal, operator, agent)
|
|
43
|
+
- Field visibility control per facet
|
|
44
|
+
- Capability access control
|
|
45
|
+
- Computed fields
|
|
46
|
+
- Feature comparison matrix
|
|
47
|
+
|
|
48
|
+
### Phase 3: API Generation
|
|
49
|
+
|
|
50
|
+
Demonstrates automatic REST API generation using Grape.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ruby 03_api_generation.rb
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Shows:**
|
|
57
|
+
- Auto-generated REST endpoints
|
|
58
|
+
- Field serialization based on facet rules
|
|
59
|
+
- Capability-to-endpoint mapping
|
|
60
|
+
- Route comparison across facets
|
|
61
|
+
|
|
62
|
+
### Phase 4: Auto-Mounting
|
|
63
|
+
|
|
64
|
+
Shows how Facera automatically discovers and mounts all facets with zero configuration.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ruby 04_auto_mounting.rb
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Shows:**
|
|
71
|
+
- Zero-config auto-mounting
|
|
72
|
+
- Custom path configuration
|
|
73
|
+
- Facet enabling/disabling
|
|
74
|
+
- Rails integration (Railtie)
|
|
75
|
+
- Rack/Sinatra integration
|
|
76
|
+
- Per-facet authentication
|
|
77
|
+
- Configuration DSL
|
|
78
|
+
|
|
79
|
+
### Running the Server
|
|
80
|
+
|
|
81
|
+
Launch a live HTTP server with multiple facet APIs:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd server
|
|
85
|
+
rackup -p 9292
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then test the APIs:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Check health
|
|
92
|
+
curl http://localhost:9292/api/v1/health
|
|
93
|
+
|
|
94
|
+
# List payments (external API)
|
|
95
|
+
curl http://localhost:9292/api/v1/payments
|
|
96
|
+
|
|
97
|
+
# Create a payment
|
|
98
|
+
curl -X POST http://localhost:9292/api/v1/payments \
|
|
99
|
+
-H 'Content-Type: application/json' \
|
|
100
|
+
-d '{
|
|
101
|
+
"amount": 100.0,
|
|
102
|
+
"currency": "USD",
|
|
103
|
+
"merchant_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
104
|
+
"customer_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
|
105
|
+
}'
|
|
106
|
+
|
|
107
|
+
# Confirm payment (internal API only - not available in external)
|
|
108
|
+
curl -X POST http://localhost:9292/api/internal/v1/payments/{id}/confirm
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Server Structure
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
server/
|
|
115
|
+
āāā config.ru # Rack server (just 38 lines!)
|
|
116
|
+
āāā config/
|
|
117
|
+
ā āāā facera.rb # Facera configuration
|
|
118
|
+
āāā cores/
|
|
119
|
+
ā āāā payment_core.rb # Domain model
|
|
120
|
+
āāā facets/
|
|
121
|
+
āāā external_facet.rb
|
|
122
|
+
āāā internal_facet.rb
|
|
123
|
+
āāā operator_facet.rb
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Convention over configuration:**
|
|
127
|
+
- `config.ru` - Just loads config and calls `Facera.auto_mount!`
|
|
128
|
+
- `config/facera.rb` - All configuration in one place
|
|
129
|
+
- `cores/` - Drop files here, auto-discovered!
|
|
130
|
+
- `facets/` - Drop files here, auto-discovered!
|
|
131
|
+
- **No manual requires needed** - Facera finds everything automatically
|
|
132
|
+
- Zero boilerplate
|
|
133
|
+
|
|
134
|
+
## Key Concepts Demonstrated
|
|
135
|
+
|
|
136
|
+
### 1. Single Source of Truth
|
|
137
|
+
Define your domain model once in the core:
|
|
138
|
+
```ruby
|
|
139
|
+
Facera.define_core(:payment) do
|
|
140
|
+
entity :payment do
|
|
141
|
+
attribute :amount, :money, required: true
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 2. Multiple Projections
|
|
147
|
+
Create different views for different consumers:
|
|
148
|
+
```ruby
|
|
149
|
+
Facera.define_facet(:external, core: :payment) do
|
|
150
|
+
expose :payment do
|
|
151
|
+
fields :id, :amount # Limited fields
|
|
152
|
+
end
|
|
153
|
+
allow_capabilities :create_payment # Limited access
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
Facera.define_facet(:internal, core: :payment) do
|
|
157
|
+
expose :payment do
|
|
158
|
+
fields :all # Full visibility
|
|
159
|
+
end
|
|
160
|
+
allow_capabilities :all # Full access
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 3. Zero Boilerplate APIs
|
|
165
|
+
Generate complete REST APIs automatically:
|
|
166
|
+
```ruby
|
|
167
|
+
api = Facera.api_for(:external)
|
|
168
|
+
# Auto-generates: POST /payments, GET /payments/:id, etc.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Next Steps
|
|
172
|
+
|
|
173
|
+
After running these examples:
|
|
174
|
+
1. Explore the generated routes in `03_api_generation.rb`
|
|
175
|
+
2. Test the live server in `server/`
|
|
176
|
+
3. Modify `server/payment_api.rb` to add your own facets
|
|
177
|
+
4. Check the test suite in `spec/` for more examples
|
|
178
|
+
|
|
179
|
+
## Tips
|
|
180
|
+
|
|
181
|
+
- **Start simple**: Run examples in order (01 ā 02 ā 03 ā server)
|
|
182
|
+
- **Experiment**: Modify `server/payment_api.rb` and restart the server
|
|
183
|
+
- **Compare facets**: Notice how external vs. internal APIs differ
|
|
184
|
+
- **Check visibility**: See how field exposure changes per facet
|