Skip to content

Commit 5d445b0

Browse files
committed
Add future usage vision and runtime integration design documents
- Introduced a new document outlining the future usage vision for AimDB, providing an illustrative example of intended ergonomics and API design. - Added a comprehensive design document for unified runtime integration and executor abstractions, detailing the goals, problem statements, success criteria, and architectural overview.
1 parent ec2ccb1 commit 5d445b0

4 files changed

Lines changed: 355 additions & 0 deletions

File tree

.github/copilot-instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ AimDB is in **early development** - the architecture is defined but implementati
1212
## Project Architecture Vision
1313
AimDB is an async, in-memory database for real-time data synchronization across **MCU → edge → cloud** environments, targeting <50ms reactivity.
1414

15+
### Future Usage Vision
16+
See `docs/vision/future_usage.md` for an aspirational (non-functional) end-user example illustrating the intended ergonomic API across runtimes. Treat it as a guide when designing traits and modules—incremental PRs should converge toward that shape without introducing unused stubs prematurely.
17+
1518
### Current Workspace Structure
1619
```
1720
aimdb-core/ # Core database engine (skeleton)

_external/embassy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 35b0ba4ce0fed7588febe504e16bbf1788384f5a
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# 🚀 Design: Unified Runtime Integration & Executor Abstractions (M1.1)
2+
3+
**Milestone**: Runtime Independence & Multi-Platform Execution (follow-up to M1)
4+
**Goal**: Establish a clean, trait-based execution & time abstraction layer powering both `std` (Tokio) and `no_std` (Embassy) environments while keeping `aimdb-core` free of concrete runtime dependencies.
5+
**Scope of Rework**: This document retrofits experimental work done on branch `8-runtime-adapter-integration-#1` (≈5K LOC delta) into a structured, incremental upstream plan.
6+
7+
---
8+
9+
## 🎯 Problem Statement
10+
11+
AimDB must run the same logical services across:
12+
- MCU / embedded boards (`no_std` + Embassy executor, static tasks, limited heap)
13+
- Edge & Cloud (`std` + Tokio, dynamic spawning, richer timing APIs)
14+
15+
Early spike code mixed concerns (database, runtime selection, spawning models, timing utilities) and added crates in one large change set, making review, testing, and stabilization difficult.
16+
17+
We need a minimal, opinionated runtime abstraction that:
18+
- ✅ Keeps `aimdb-core` runtime-agnostic (traits only)
19+
- ✅ Supports both dynamic (Tokio) and static (Embassy) spawn models
20+
- ✅ Unifies time APIs (now, sleep, delayed spawn) without leaking concrete types
21+
- ✅ Scales to additional runtimes (custom executor, wasm) later
22+
- ✅ Works under `no_std` (no hidden allocations) & `std` (full ergonomics)
23+
- ✅ Enables service definition via macro (`#[service]`) that remains runtime-neutral
24+
25+
---
26+
27+
## ✅ Success Criteria
28+
29+
Functional:
30+
- [ ] `aimdb-executor` crate defines core traits: `RuntimeAdapter`, `SpawnDynamically`, `SpawnStatically`, `TimeSource`
31+
- [ ] `aimdb-core` depends only on `aimdb-executor` (no Tokio/Embassy direct deps)
32+
- [ ] Tokio & Embassy adapters implement full trait surface behind feature flags
33+
- [ ] `#[service]` macro generates a runtime-neutral service struct + spawn helpers
34+
- [ ] Unified sleep / timestamp access via `TimestampProvider` + `SleepCapable` (or consolidated `TimeSource`)
35+
- [ ] Database run loop uses only trait bounds (no cfg spaghetti internally)
36+
37+
Non-Functional:
38+
- [ ] <50ms end-to-end service reaction across platforms
39+
- [ ] Zero heap allocations in Embassy path (tracked via tests / `alloc` feature absence)
40+
- [ ] No increase in core public API surface instability (documented semver intent)
41+
- [ ] Code size growth of `no_std` binary attributable per feature flag
42+
- [ ] CI matrix covers: core (no_std), core+tokio, core+embassy
43+
44+
---
45+
46+
## 🧩 Architecture Overview
47+
48+
Mermaid Diagram:
49+
50+
```mermaid
51+
flowchart TB
52+
App[Application] -->|expands| Macro[[#[service] macro]]
53+
Macro --> Service[Generated Service Struct]
54+
Service --> Core[aimdb-core]
55+
Core --> Executor[aimdb-executor traits]
56+
AdapterTokio[Tokio Adapter] --> Executor
57+
AdapterEmbassy[Embassy Adapter] --> Executor
58+
subgraph Adapters
59+
AdapterTokio
60+
AdapterEmbassy
61+
end
62+
```
63+
64+
Key Points:
65+
1. `aimdb-core` depends only on trait contracts (`aimdb-executor`).
66+
2. Adapters implement those traits; core never imports adapter symbols.
67+
3. Services are generic; macro output remains runtime-neutral.
68+
4. Application chooses which adapter(s) to instantiate and inject.
69+
70+
### Core Trait Set (from `aimdb-executor`)
71+
| Trait | Responsibility | Notes |
72+
|-------|----------------|-------|
73+
| `RuntimeAdapter` | Identity + construction | Minimal: `new()`, `runtime_name()` |
74+
| `SpawnDynamically` | `spawn(Future)` returning JoinHandle | Tokio style |
75+
| `SpawnStatically` | Static task provisioning (`Spawner`) | Embassy style |
76+
| `TimeSource` | `now()`, `sleep(duration)`, conversion helpers | Abstracts time types |
77+
78+
> Layering Clarification: Adapters DO NOT "implement aimdb-core". They implement the trait contracts defined in `aimdb-executor`. The `aimdb-core` crate depends only on those traits (inverting the dependency) and remains unaware of adapter concrete types. Wording elsewhere should be: “Tokio/Embassy adapters implement the executor & time traits consumed by aimdb-core,” not “implement aimdb-core.”
79+
80+
### Time Abstraction
81+
Two dominant ecosystems (Tokio `std::time`, Embassy `embassy_time`). `TimeSource` unifies:
82+
```rust
83+
trait TimeSource: RuntimeAdapter {
84+
type Instant; // runtime-specific
85+
type Duration; // std::time::Duration | embassy_time::Duration
86+
fn now(&self) -> Self::Instant;
87+
fn sleep(&self, d: Self::Duration) -> impl Future<Output = ()>;
88+
fn millis(&self, ms: u64) -> Self::Duration; // constructor helpers
89+
}
90+
```
91+
92+
### Service Macro (`aimdb-macros::service`)
93+
Input: `async fn my_service(ctx: RuntimeContext<R>) -> DbResult<()> { ... }`
94+
Generates:
95+
- Original function retained
96+
- `struct MyService;`
97+
- `MyService::spawn_tokio(&TokioAdapter)` (feature gated)
98+
- `MyService::spawn_embassy(&'static EmbassyAdapter)` (feature gated)
99+
Ensures service logic stays executor-agnostic.
100+
101+
### Runtime Context
102+
`RuntimeContext<R>` wraps runtime adapter instance and exposes unified helpers for services (sleep, spawn child tasks, timestamps) w/out leaking adapter concrete type outside.
103+
104+
---
105+
106+
## 🛠 current branch DELTA (summary)
107+
108+
| Category | Added | Modified | Notes |
109+
|----------|-------|----------|-------|
110+
| New Crates | `aimdb-executor`, `aimdb-macros` || Provide traits + macro |
111+
| Core | context.rs, runtime.rs, time.rs, database.rs | lib.rs, error.rs | Pulls traits + exposes reexports |
112+
| Adapters | embassy + tokio runtime/time/database modules | existing Cargo.toml | Feature gating WIP |
113+
| Examples | tokio + embassy runtime demos | removed single `quickstart.rs` | Split per-runtime |
114+
| Tooling | `.gitmodules` (Embassy), Makefile edits | CI workflows | Introduced external dependency |
115+
116+
Risk: Monolithic merge obscures review; unstable patterns might ossify.
117+
118+
---
119+
120+
## 🔄 Refactoring / Reimplementation Plan
121+
122+
Phased approach to upstream clean, reviewable slices:
123+
124+
### Phase 0 – Baseline Preparation
125+
1. Extract ONLY `aimdb-executor` crate with minimal traits (no macros yet)
126+
2. Wire feature flags at workspace root (ensure no implicit default coupling)
127+
3. CI: add matrix (core-only, tokio, no_std check build)
128+
129+
### Phase 1 – Core Integration
130+
4. Refactor `aimdb-core` to depend on `aimdb-executor` traits; remove any direct runtime references
131+
5. Introduce `RuntimeContext` (lightweight) exposing time + spawn abstractions
132+
6. Add doc tests showing generic service using `RuntimeContext<R>`
133+
134+
### Phase 2 – Tokio Adapter
135+
7. Implement `TokioAdapter` with `SpawnDynamically + TimeSource`
136+
8. Provide basic database run example (tokio demo) – no macro yet
137+
9. Add integration test: spawn service + verify sleep timing tolerance
138+
139+
### Phase 3 – Embassy Adapter
140+
10. Vendor Embassy submodule (document licensing + update CONTRIBUTING)
141+
11. Implement `EmbassyAdapter` with static spawn API (behind `embassy-runtime`)
142+
12. Provide `no_std` build test (thumb target) in CI (compile-only)
143+
144+
### Phase 4 – Service Macro
145+
13. Introduce `aimdb-macros` with `#[service]` generating service struct
146+
14. Gate spawn helpers per runtime feature, ensure no accidental std usage in embassy path
147+
15. Add macro snapshot tests (compiletest/minitest harness)
148+
149+
### Phase 5 – Unified Time Utilities
150+
16. Finalize `TimeSource` shape (evaluate folding SleepCapable/TimestampProvider)
151+
17. Replace ad-hoc time modules in adapters with canonical implementation
152+
18. Bench micro-latency: spawn + immediate sleep vs native runtime baseline (<5% overhead)
153+
154+
### Phase 6 – Examples & Docs
155+
19. Replace experimental examples with curated `examples/{tokio,embassy}-runtime-demo`
156+
20. Add README section “Choosing a Runtime” linking to design docs
157+
21. Add architecture diagram asset if needed
158+
159+
### Phase 7 – Cleanup & Deprecation
160+
22. Remove legacy spike modules not used after refactor
161+
23. Ensure semver guardrails: mark new APIs experimental via doc `#[doc(cfg(...))]`
162+
24. Close tracking issues & retire integration branch
163+
164+
---
165+
166+
## 🧮 Feature Flags (proposed normalized set)
167+
```toml
168+
[features]
169+
default = ["tokio-runtime"] # Consider removing default to force explicit choice
170+
tokio-runtime = ["dep:tokio"]
171+
embassy-runtime = ["dep:embassy-executor", "dep:embassy-time"]
172+
macros = ["aimdb-macros"]
173+
services = ["macros"] # Higher-level convenience grouping
174+
std = [] # Forwarded where needed
175+
```
176+
177+
---
178+
179+
## ⚠️ Risks & Mitigations
180+
| Risk | Impact | Mitigation |
181+
|------|--------|------------|
182+
| Trait proliferation | API bloat | Keep minimal initial trait set; postpone advanced scheduling |
183+
| Embassy static model mismatch | Hard to generalize spawn | Keep separate trait (`SpawnStatically`) vs forcing unification |
184+
| Macro hides complexity | Debug difficulty | Generate readable code; provide `--emit=expanded` doc snippet |
185+
| Time abstraction cost | Performance regression | Micro-bench spawn + sleep vs raw runtime |
186+
| Submodule churn | CI instability | Pin commit hash + document update procedure |
187+
188+
---
189+
190+
## 📌 Issue Breakdown (to create)
191+
Reference each to this design doc section.
192+
193+
1. Add `aimdb-executor` crate (Phase 0–1)
194+
2. Integrate executor traits into `aimdb-core` (Phase 1)
195+
3. Introduce `RuntimeContext` with docs (Phase 1)
196+
4. Implement `TokioAdapter` + basic example (Phase 2)
197+
5. Add Tokio integration tests for spawn & sleep (Phase 2)
198+
6. Vendor Embassy submodule + licensing notes (Phase 3)
199+
7. Implement `EmbassyAdapter` skeleton (Phase 3)
200+
8. Add `thumbv7em` compile check in CI (Phase 3)
201+
9. Introduce `aimdb-macros` crate + `#[service]` (Phase 4)
202+
10. Add macro expansion tests (Phase 4)
203+
11. Finalize `TimeSource` and remove redundant traits (Phase 5)
204+
12. Add performance benchmarks (Phase 5)
205+
13. Replace examples with curated runtime demos (Phase 6)
206+
14. Update README & diagrams (Phase 6)
207+
15. Remove spike code & retire branch (Phase 7)
208+
16. Add semver stability notes & doc cfg attributes (Phase 7)
209+
210+
---
211+
212+
## 🔓 Open Questions
213+
- Should `RuntimeContext` own or borrow the adapter? (Current spike clones; may prefer Arc for std & &'static for no_std)
214+
- Provide a unified delayed-spawn API or leave to runtime-specific extension traits?
215+
- Should service macro support concurrency limits / restart policies now or later?
216+
- Do we need a mock runtime crate for deterministic tests? (Recommended Phase 2.5 optional)
217+
218+
---
219+

docs/vision/future_usage.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# 🌅 AimDB Future Usage Vision (Illustrative)
2+
3+
Status: NON-FUNCTIONAL EXAMPLE (forward-looking API sketch).
4+
Purpose: Communicate intended ergonomics & guide incremental implementation.
5+
6+
---
7+
8+
## High-Level Scenario
9+
Edge gateway mirrors sensor stream (MCU → Edge) and republishes aggregated snapshots to cloud consumers with sub-50ms latency, using a unified API across runtimes.
10+
11+
## Goals Demonstrated
12+
- Runtime neutrality (Tokio vs Embassy) via adapters
13+
- Service definition via `#[service]` macro
14+
- Reactive subscription to record changes
15+
- Low-latency publish path using zero-copy views (future)
16+
17+
---
18+
19+
## 1. Service Definition (Runtime Agnostic)
20+
```rust,ignore
21+
use aimdb_core::{DbResult, RuntimeContext};
22+
use aimdb_macros::{service, Record};
23+
24+
/// strongly typed record (future derive macro)
25+
#[derive(Debug, PartialEq, Clone, Record)]
26+
pub struct Sensor {
27+
temp: f32,
28+
humidity: f32,
29+
}
30+
31+
/// Periodically samples sensor data and writes to the database
32+
#[service]
33+
pub async fn sensor_poll_service<R: RuntimeAdapter>(ctx: RuntimeContext<R>) -> DbResult<()> {
34+
let log = ctx.log();
35+
let time = ctx.time();
36+
let records = ctx.records();
37+
38+
log.info("sensor_poll_service:start");
39+
let interval = time.millis(500);
40+
loop {
41+
let reading = acquire_sensor_reading()?; // future: no_std HAL integration
42+
records.set::<Sensor>(reading.to_sensor_data()).await?; // unified verb: set
43+
time.sleep(interval).await; // accessor style for sleep
44+
}
45+
}
46+
```
47+
48+
## 2. Subscription / Reactive Consumer
49+
```rust,ignore
50+
51+
#[derive(Debug, PartialEq, Clone, Record)]
52+
pub struct Derived {
53+
heat_index: f32,
54+
}
55+
56+
#[service]
57+
pub async fn aggregation_service<R: RuntimeAdapter>(ctx: RuntimeContext<R>) -> DbResult<()> {
58+
let log = ctx.log();
59+
let records = ctx.records();
60+
let metrics = ctx.metrics();
61+
62+
let mut sub = records.subscribe::<Sensor>().await?; // typed streaming cursor
63+
while let Some(sensor) = sub.next().await {
64+
metrics.increment("sensor_updates");
65+
let derived = Derived { heat_index: calc_heat_index(sensor.temp) };
66+
records.set::<Derived>(derived).await?;
67+
}
68+
Ok(())
69+
}
70+
```
71+
72+
## 3. Application Wiring (Tokio)
73+
```rust,ignore
74+
#[tokio::main]
75+
async fn main() -> DbResult<()> {
76+
77+
let spec = DatabaseSpec::<TokioAdapter>::builder()
78+
.record::<Sensor>().max_buffer(10)
79+
.service(sensor_poll_service).max_subscribers(5)
80+
.record::<Derived>().max_buffer(5)
81+
.service(aggregation_service).max_subscribers(5)
82+
.build();
83+
let db = new_database(spec)?; // envisioned helper
84+
85+
// Reactive query
86+
let mut heat_sub = db.subscribe<Derived>().await?;
87+
while let Some(v) = heat_sub.next().await { println!("heat index={v}"); }
88+
Ok(())
89+
}
90+
```
91+
92+
## 4. Application Wiring (Embassy - Pseudocode)
93+
```rust,ignore
94+
#[embassy::main]
95+
async fn main(spawner: Spawner) -> ! {
96+
// Build spec similarly (pseudocode) – static tasks may be pre-allocated
97+
let spec = DatabaseSpec::<EmbassyAdapter>::builder()
98+
.record::<Sensor>().max_buffer(10)
99+
.service(sensor_poll_service).max_subscribers(5)
100+
.record::<Derived>().max_buffer(5)
101+
.service(aggregation_service).max_subscribers(5)
102+
.build();
103+
let db = new_database(spawner, spec)?; // envisioned helper
104+
105+
loop { db.adapter().time().sleep(adapter.millis(1000)).await; }
106+
}
107+
```
108+
109+
## 5. Desired Ergonomic Properties
110+
- `RuntimeContext` unifies: sleep, time, spawn child task, db access, metrics handle.
111+
- Builder pattern for database spec: `Database::builder().with_ring_buffer(64).with_notifications().build()`
112+
- Subscriptions provide backpressure-ready async stream interface.
113+
- Zero-copy `get_view()` for hot path reads (future optimization).
114+
115+
## 6. Open Design Questions (Flagged for Issues)
116+
| Topic | Question | Notes |
117+
|-------|----------|-------|
118+
| Subscription semantics | Bounded vs unbounded channels? | Backpressure strategy TBD |
119+
| Metrics | Built-in vs adapter-provided? | Possibly trait with no_std stub |
120+
| Caching | Where to host sliding windows? | Likely feature-gated module |
121+
| Serialization | pluggable serde vs custom binary | Affects zero-copy design |
122+
| Persistence | WAL / snapshot layering | Out of scope for initial runtime milestone |
123+
124+
## 7. Non-Goals (For This Milestone)
125+
- Persistence durability guarantees
126+
- Cross-node replication protocol
127+
- Security/auth layers
128+
- Full metrics backend integration
129+
130+
---
131+
132+
This document is intentionally aspirational; real APIs will land incrementally. Keep it updated as design decisions solidify.

0 commit comments

Comments
 (0)