Skip to content

Commit d0e7ea2

Browse files
committed
wip
1 parent d74b77d commit d0e7ea2

File tree

1 file changed

+65
-17
lines changed

1 file changed

+65
-17
lines changed

docs/news/posts/2025-09-05-orm.md

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
blogpost: true
33
category: Blog
4-
tags: usability
4+
tags: architecture
55
author: Julian Geiger
66
date: 2025-09-05
77
---
@@ -11,11 +11,10 @@ date: 2025-09-05
1111
AiiDA provides an Object-Relational Mapping (ORM) system that abstracts database operations while supporting multiple database backends.
1212
In this post, we'll explore how AiiDA leverages SQLAlchemy to create a flexible, multi-backend ORM to separate concerns between Python objects and database persistence.
1313

14-
## Architecture Overview: The Three-Layer Design
14+
## Architecture overview: The multi-layer design
1515

16-
AiiDA's ORM follows a three-layer architecture that provides clean separation between the user interface, business logic, and data persistence:
16+
AiiDA's ORM follows a four-layer architecture that provides clean separation between the user interface, business logic, and data persistence:
1717

18-
<!-- TODO: check again, we have 4 top-level sections, but only 3 here -->
1918
```
2019
┌─────────────────────┐
2120
│ User Interface │ ← Node (Python ORM class)
@@ -24,8 +23,11 @@ AiiDA's ORM follows a three-layer architecture that provides clean separation be
2423
│ Backend Interface │ ← BackendNode (Abstract base class)
2524
│ (implementation) │
2625
├─────────────────────┤
27-
│ Database Backends │ ← SqlaNode (SQLAlchemy implementation)
28-
│ (psql_dos/sqlite) │
26+
│ Database Backends │ ← SqlaNode (SQLAlchemy implementation)
27+
│ (psql_dos/sqlite) │
28+
├─────────────────────┤
29+
│ Database Layer │ ← DBNode (SQLAlchemy models)
30+
│ (models) │
2931
└─────────────────────┘
3032
```
3133

@@ -41,7 +43,7 @@ The three layers serve distinct purposes:
4143

4244
So let's start from the bottom, shall we?
4345

44-
## The database: SQLAlchemy models
46+
## The database: `DbNode`
4547

4648
AiiDA uses SQLAlchemy's declarative approach to define database tables.
4749
This means the database schema is defined using Python classes rather than raw SQL.
@@ -221,7 +223,7 @@ This design provides:
221223

222224
__Storing data in the DB__
223225

224-
Actual node data storage in the SQL db is achieved through the `store` method of the `SqlaNode`:
226+
Actual data storage in the SQL db is achieved through the `store` method of the `SqlaNode`:
225227

226228
```python
227229
def store(self, links=None, clean=True):
@@ -250,6 +252,7 @@ def store(self, links=None, clean=True):
250252

251253
This approach ensures data consistency while supporting nested transactions for complex operations.
252254

255+
!!! info ""
253256

254257
## The user interface: `Node`
255258

@@ -324,7 +327,7 @@ It also uses @cached_property to ensure these namespace objects are created only
324327

325328
## Honorable mentions
326329

327-
### Link Management: Modeling Relationships
330+
### Link management: modeling relationships
328331

329332
AiiDA models relationships between nodes through a separate `DbLink` table:
330333

@@ -350,7 +353,32 @@ The `label` field describes the specific role of the link (e.g., "structure", "p
350353
This metadata enables complex provenance queries like "find all structures that were relaxed using PBE exchange-correlation functional."
351354
The `ondelete='CASCADE'` on the output relationship ensures that when a node is deleted, all its incoming links are also removed, maintaining referential integrity.
352355

353-
### Pydantic Model Integration: Modern Serialization
356+
### The QueryBuilder
357+
358+
The `QueryBuilder` is AiiDA's main API provided to retrieve data from the database.
359+
It provides a uniform, backend-agnostic interface:
360+
361+
```python
362+
# Simple node query
363+
qb = QueryBuilder()
364+
qb.append(Node, filters={'label': {'like': 'calculation%'}})
365+
results = qb.all()
366+
367+
# Complex relationship query
368+
qb = QueryBuilder()
369+
qb.append(StructureData, tag='structure')
370+
qb.append(CalcJobNode, with_incoming='structure', tag='calc')
371+
qb.append(FolderData, with_incoming='calc', project=['*'])
372+
```
373+
374+
The QueryBuilder automatically:
375+
376+
* __Converts ORM Classes__: Maps Node classes to their corresponding database entities
377+
* __Handles Relationships__: Translates with_incoming/with_outgoing to proper SQL joins
378+
* __Type Filtering_: Automatically adds filters for node types and subtypes
379+
* __Backend Translation__: Converts the same query syntax to PostgreSQL or SQLite SQL
380+
381+
### Pydantic models: Modern Serialization
354382

355383
Recently, AiiDA incorporated Pydantic models for modern data validation and serialization:
356384

@@ -401,15 +429,37 @@ This enables:
401429
- **API Integration**: Direct compatibility with FastAPI and other modern frameworks
402430
- **Schema Documentation**: Auto-generated OpenAPI schemas for web interfaces
403431

404-
### NodeCollections
432+
### NodeCollections: The interface pattern
405433

406-
### Querybuilder
434+
The `NodeCollection` class provides a clean interface for managing collections of nodes:
435+
436+
```python
437+
class NodeCollection(EntityCollection[NodeType], Generic[NodeType]):
438+
"""The collection of nodes."""
439+
440+
def delete(self, pk: int) -> None:
441+
"""Delete a Node from the collection with the given id"""
442+
node = self.get(id=pk)
443+
444+
if node.base.links.get_incoming().all():
445+
raise exceptions.InvalidOperation(f'cannot delete Node<{node.pk}> because it has incoming links')
446+
447+
if node.base.links.get_outgoing().all():
448+
raise exceptions.InvalidOperation(f'cannot delete Node<{node.pk}> because it has outgoing links')
449+
450+
self._backend.nodes.delete(pk)
451+
```
452+
This collection pattern provides several benefits:
453+
454+
* __Type safety__: Generic typing ensures you get the correct node type back
455+
* __Validation__: Prevents deletion of nodes with existing links to maintain provenance integrity
456+
* __Backend abstraction__: Hides database-specific operations behind a clean interface
407457

408458
## Key Takeaways
409459

410-
AiiDA's ORM architecture demonstrates several important design principles:
460+
AiiDA's ORM architecture makes use of several important design principles:
411461

412-
1. **Multi-Layer Abstraction**: The three-layer design (User Interface → Backend Interface → Database Implementation) provides clean separation while allowing backend-specific optimizations.
462+
1. **Multi-Layer Abstraction**: The multi-layer design (User Interface → Backend Interface → Database Implementation) provides clean separation while allowing backend-specific optimizations.
413463

414464
2. **Automatic Backend Adaptation**: The PostgreSQL-to-SQLite conversion system shows how to maintain feature parity across different database engines without code duplication.
415465

@@ -425,9 +475,7 @@ AiiDA's ORM architecture demonstrates several important design principles:
425475

426476
8. **Collection Patterns**: The collection system provides a consistent, intuitive interface for data access that scales from simple lookups to complex queries.
427477

428-
This architecture allows AiiDA to provide a powerful, flexible ORM that adapts to different database backends while maintaining a consistent user experience. The careful balance between abstraction and performance, combined with modern Python practices, makes it an excellent example of how to build scalable scientific software that can evolve with changing requirements and technologies.
429-
430-
The system's ability to automatically convert PostgreSQL models to SQLite equivalents, while maintaining query compatibility through sophisticated backend-specific implementations, demonstrates how thoughtful architecture can provide both flexibility and performance without compromising on either.
478+
This architecture allows AiiDA to provide a powerful, flexible ORM that adapts to different database backends while maintaining a consistent user experience.
431479

432480
<!-- TODO: -->
433481
<!-- Add statement that the whole infrastructure also had to be implemented for the other, specialized data types of AiiDA -->

0 commit comments

Comments
 (0)