Home → Documentation → Architecture → Principles
Every other principle in this document is a refinement of these five. If a design decision can’t be justified by at least one of them, it doesn’t belong in Foundgine.
1. Business First The domain is the source of truth. Infrastructure exists to serve it.
2. Compile-Time by Default Discover as much as possible during compilation. Avoid runtime reflection and dynamic behavior whenever practical.
3. Deterministic Execution Every request should execute through a known, generated execution plan. Predictability is more valuable than hidden magic.
4. Provider-Based Architecture Execution is delegated to providers. Today that may be PostgreSQL. Tomorrow it may be SQL Server, Kafka, Temporal, Redis, or something else. The planner doesn’t change.
5. Transport Agnostic GraphQL is not special. Neither is REST. Neither is gRPC. They are simply ways of entering the execution engine.
These are the day-to-day engineering principles that fall out of the five core principles above — stable guidance for anyone implementing a new provider, transport, or generator stage.
This document captures the fundamental engineering principles that guide every architectural and implementation decision within Foundgine. These principles are intentionally long-lived and should remain stable even as individual implementations evolve.
Foundgine is designed around a simple idea:
Move complexity to compile time so runtime can remain simple, deterministic, and fast.
Every architectural decision should reinforce this objective.
Anything that can be computed during compilation should never be computed during execution.
Examples include:
The Runtime should execute prepared artifacts rather than discover information dynamically.
Runtime exists to execute.
It should never perform:
Execution should always operate on immutable, precomputed inputs.
Every architectural layer owns one responsibility.
| Layer | Responsibility |
|---|---|
| Foundation | Contracts |
| Runtime | Execution |
| SQL | SQL serialization |
| Generator | Compile-time analysis |
| GraphQL | Transport |
| Generated Code | Precomputed data |
Responsibilities should not overlap.
High-level components should depend on abstractions rather than generated implementations.
Instead of:
Runtime
↓
GeneratedMetadata
Prefer:
Runtime
↓
IMetadataProvider
↓
GeneratedMetadataProvider
Generated code becomes a replaceable implementation rather than an architectural dependency.
Metadata represents facts about the application.
Facts should not change while the application is running.
Metadata objects should therefore be:
Examples include:
Planning determines execution.
Execution should not modify planning decisions.
QueryPlan and MutationPlan should therefore be immutable representations of work to perform.
Dependencies should always be visible.
Hidden dependencies, service locators, and implicit behavior should be avoided.
Architecture should be understandable by reading project references.
GraphQL is one transport—not the framework.
The same Runtime should execute requests originating from:
Execution semantics remain identical regardless of transport.
Planning should remain independent of storage engines.
Only SQL serialization changes between providers.
Potential providers include:
The planner should not require modification.
Running the Generator twice on identical source code should produce identical generated output.
Deterministic generation simplifies:
Native AOT is not a separate feature.
It is a consequence of good architecture.
Avoid:
Prefer generated implementations and static dispatch.
Performance should result from architectural choices rather than isolated optimizations.
Examples include:
Architecture should eliminate work rather than optimize unnecessary work.
Framework behavior should be composed through interfaces.
Prefer:
IMetadataProvider
ISqlDialect
IGraphStrategy
Avoid deep inheritance hierarchies.
Composition improves flexibility and testing.
Execution should be deterministic.
Given the same:
the framework should produce identical results.
Predictability simplifies debugging and testing.
Every major component should be testable in isolation.
Foundation should not require Runtime.
Runtime should not require SQL.
SQL should not require GraphQL.
Generator output should be snapshot tested.
Architecture should naturally encourage testing.
Code is read more often than it is written.
Prefer explicit implementations over clever abstractions.
Generated code should be understandable.
Runtime should be easy to debug.
Simple code generally performs well enough and is easier to maintain.
Foundation represents the public architectural vocabulary.
Changes to Foundation should be deliberate and infrequent.
Stable contracts reduce churn throughout the framework.
Extension points should be explicit.
Applications should customize behavior through interfaces rather than modifying Runtime.
Examples include:
Every layer should know only what it needs.
Foundation
↑
Runtime
↑
Transport
No layer should bypass another through direct implementation knowledge.
Foundgine is intended to evolve over many years.
Short-term convenience should never compromise long-term architectural consistency.
When evaluating new features, prioritize:
over minimal implementation effort.
These principles define the architectural identity of Foundgine.
They guide every decision—from project organization and source generation to SQL serialization and runtime execution.
When multiple implementation options exist, the preferred choice is the one that best preserves:
By consistently applying these principles, Foundgine remains performant, maintainable, extensible, and adaptable as the framework continues to grow.
| ← Previous: Vision | Next: Layers → |