Chapter 13 · Application Drivers and Bolt: Sessions, Routing, Parameters, Result Streaming, and Connection Pools

Build a Repository/Service Layer that Keeps Cypher Parameterized, Transactional, Testable, and Measurable

Assemble the driver, repository and service boundaries into a parameterized, retry-safe, testable and observable AtlasMart application layer.

Advanced180–230 minutesRepository/service integration labNeo4j 2026.07.1 Community · Cypher 25Python driver 6.3.0 · Bolt 6/5 awareLast reviewed: September 2026

Learning outcomes

The final chapter lab turns the mechanisms into an application boundary: one maintained Driver, a repository that owns Cypher and result transformation, and a service that owns business inputs, idempotency and structured errors. The goal is not a framework-specific architecture; it is to make every database interaction parameterized, transactional, testable and measurable.

01

Construct one maintained driver with explicit database, pool and retry configuration.

02

Keep Cypher text and parameters inside repository methods with stable return types.

03

Keep multi-query business invariants inside managed transactions and external side effects outside retry callbacks.

04

Attach request/operation metadata and capture result summaries for client/server latency decomposition.

05

Test repository behavior against the deterministic AtlasMart fixture and failure cases without leaking driver resources.

Chapter 13 baseline · reviewed 9 September 2026

The mandatory lab continues Neo4j Community 2026.07.1, database neo4j, explicit CYPHER 25 where query-language version matters, container atlasmart-neo4j, loopback Bolt 127.0.0.1:7687, authentication neo4j/atlasmart-course-2026, and no TLS on the loopback-only disposable instance. The current official Python driver is neo4j 6.3.0 (Python 3.10–3.14), whose API supports Bolt 6.0–6.1, Bolt 5.0–5.8 and Bolt 4.4. Neo4j 5.26.30 remains the LTS comparison line.

Evidence boundary

This generation environment does not connect to the AtlasMart Neo4j container, so no handshake, routing-table, pool, TLS, bookmark, latency or retry output is fabricated. Each exercise gives commands and invariants to capture on your machine. True multi-member read/write routing requires an Enterprise cluster or Aura deployment; the mandatory Community path proves the driver/session/pool/stream semantics locally and labels cluster-only observations separately.

1. Minimal repository/service ownership model

Layer Owns Must not own
HTTP/CLI handler transport validation, request ID, response mapping raw Cypher string construction
Service business workflow, idempotency key, authorization decision, external side-effect orchestration connection pool lifecycle per call
Repository parameterized Cypher, transaction boundary, Neo4j result transformation HTTP response objects or unrelated business side effects
Driver bootstrap URI/auth/TLS/pool/retry configuration and shutdown request-specific mutable state

2. Repository implementation

Python · atlasmart_repository.py
from __future__ import annotationsfrom dataclasses import dataclassfrom neo4j import Driver, Query, unit_of_work@dataclass(frozen=True)class OrderView:    order_id: str    status: strclass AtlasMartRepository:    def __init__(self, driver: Driver, database: str = "neo4j"):        self._driver = driver        self._database = database    def get_order(self, order_id: str, request_id: str) -> OrderView | None:        query = Query(            """            CYPHER 25            MATCH (o:Order {orderId:$orderId})            RETURN o.orderId AS orderId, o.status AS status            """,            timeout=3.0,            metadata={"app":"atlasmart-api", "request_id":request_id,                      "operation":"get-order"},        )        with self._driver.session(database=self._database) as session:            result = session.run(query, orderId=order_id)            record = result.single()            summary = result.consume()            if record is None:                return None            return OrderView(record["orderId"], record["status"])    @staticmethod    @unit_of_work(timeout=5.0)    def _upsert_order_tx(tx, order_id: str, customer_id: str, status: str):        return tx.run(            """            CYPHER 25            MATCH (c:Customer {customerId:$customerId})            MERGE (o:Order {orderId:$orderId})            ON CREATE SET o.createdAt=datetime()            SET o.status=$status            MERGE (c)-[:PLACED]->(o)            RETURN o.orderId AS orderId, o.status AS status            """,            orderId=order_id, customerId=customer_id, status=status,        ).single(strict=True).data()    def upsert_order(self, order_id: str, customer_id: str, status: str):        with self._driver.session(database=self._database) as session:            return session.execute_write(                self._upsert_order_tx, order_id, customer_id, status)

The repository returns Python-owned values, not a live Result. The managed write can retry, so orderId is a stable caller-generated identity backed by the uniqueness constraint established in earlier chapters.

3. Application bootstrap: one Driver, explicit shutdown

Python · app.py
import osfrom neo4j import GraphDatabasefrom atlasmart_repository import AtlasMartRepositoryURI = os.getenv("NEO4J_URI", "bolt://127.0.0.1:7687")AUTH = (os.getenv("NEO4J_USER", "neo4j"),        os.getenv("NEO4J_PASSWORD", "atlasmart-course-2026"))driver = GraphDatabase.driver(    URI, auth=AUTH,    max_connection_pool_size=20,    connection_acquisition_timeout=5.0,    connection_timeout=5.0,    max_connection_lifetime=1800.0,    max_transaction_retry_time=15.0,)try:    driver.verify_connectivity()    repo = AtlasMartRepository(driver, "neo4j")    print(repo.get_order("O-5001", "req-local-001"))    print(repo.upsert_order("O-SVC-1301", "C-1001", "PAID"))finally:    driver.close()

For a real web framework, create this Driver during process/application startup and close it during graceful shutdown. Do not place credentials in source code; the defaults here continue the disposable local lab only.

4. Test deterministic behavior and repeatability

Cypher · ensure constraints and fixture
CYPHER 25CREATE CONSTRAINT customer_id IF NOT EXISTSFOR (c:Customer) REQUIRE c.customerId IS UNIQUE;CREATE CONSTRAINT order_id IF NOT EXISTSFOR (o:Order) REQUIRE o.orderId IS UNIQUE;CYPHER 25MERGE (c:Customer {customerId:'C-1001'})SET c.name='Mina Rahimi', c.tier='GOLD'MERGE (p:Product {productId:'P-1001'})SET p.name='Trail Camera', p.category='Cameras', p.price=129.90MERGE (o:Order {orderId:'O-5001'})SET o.status='PAID', o.orderedAt=datetime('2026-09-08T16:30:00Z')MERGE (c)-[:PLACED]->(o)MERGE (o)-[r:CONTAINS]->(p)SET r.quantity=1, r.unitPrice=129.90;
Python · integration assertions
repo = AtlasMartRepository(driver, "neo4j")assert repo.get_order("missing", "test-missing") is Nonefirst = repo.upsert_order("O-SVC-1301", "C-1001", "PAID")second = repo.upsert_order("O-SVC-1301", "C-1001", "PAID")assert first == second == {"orderId":"O-SVC-1301", "status":"PAID"}records, summary, _ = driver.execute_query(    """    CYPHER 25    MATCH (:Customer {customerId:'C-1001'})-[r:PLACED]->(o:Order {orderId:'O-SVC-1301'})    RETURN count(o) AS orders, count(r) AS placedEdges    """, database_="neo4j")row = records[0]assert row["orders"] == 1assert row["placedEdges"] == 1

Run the same test multiple times. The evidence is not merely “no exception”; it is the invariant that one business order and one PLACED edge exist after repeated execution.

5. Measure client/server boundaries

Python · summary timing and request timing
from time import perf_counterrequest_id = "req-measure-1301"t0 = perf_counter()records, summary, _ = driver.execute_query(    """    CYPHER 25    MATCH (c:Customer {customerId:$id})-[:PLACED]->(o:Order)    RETURN o.orderId AS orderId    ORDER BY o.orderedAt DESC, o.orderId    """,    id="C-1001", database_="neo4j")end_to_end_ms = (perf_counter() - t0) * 1000print({    "request_id": request_id,    "records": len(records),    "result_available_ms": summary.result_available_after,    "result_consumed_ms": summary.result_consumed_after,    "client_end_to_end_ms": end_to_end_ms,})

The difference between driver summary timings and end-to-end client time can include pool wait, network transfer, result transformation, application scheduling and serialization. Treat the breakdown as evidence for where to investigate, not as a perfect distributed trace.

6. Failure matrix: prove behavior, not slogans

Injected condition Expected class of evidence Correct response
wrong password authentication exception at connectivity/query time fail startup/request according to secret-rotation policy; do not generic-retry forever
pool too small + long-held results acquisition wait/timeouts and higher client tail latency reduce connection hold time or capacity-plan pool/DB; do not jump to unbounded pool
session shared concurrently debug/resource/concurrency failure create separate sessions; keep shared Driver
missing order zero-row application result return domain not-found; not a database exception
retryable transient failure managed callback may rerun idempotent transaction and bounded retry budget
client timeout after write commit outcome may be ambiguous reconcile by stable order/idempotency key

7. Cleanup/reset

Cypher · remove only Chapter 13 lab entities
CYPHER 25MATCH (o:Order)WHERE o.orderId IN ['O-DRIVER-1301','O-BOOKMARK-1','O-SVC-1301']DETACH DELETE o;MATCH (r:Reservation {reservationId:'RSV-1301'}) DETACH DELETE r;

Do not detach-delete generic AtlasMart customers/products/orders from earlier chapters. This cleanup is intentionally scoped to IDs created by this chapter.

Production judgment

Review area Acceptance evidence
API ownership all query values parameterized; repository owns result transformation; no live cursor leaks
pool/timeouts concurrency test records p50/p95/p99, acquisition failures and server saturation together
retry/idempotency stable keys + uniqueness constraints + repeated-execution invariant tests
security TLS/auth policy matches deployment; secrets externalized; no debug logs expose credentials
routing/causality cluster/Aura tests cover reader/writer placement and only necessary bookmark chains
observability request/operation metadata and client/server timing are correlated
upgrade server 2026.x / driver 6.x compatibility and application Cypher regression tests are part of rollout

Check your understanding

  1. Where should the Driver live in a web app?
  2. What should a repository return instead of Result?
  3. Why does repeated upsert testing matter?
  4. What does result_available_after not include?
  5. What is the bridge to Chapter 14?
Review the answers

1. At application/process scope, created at startup and closed on graceful shutdown.

2. Application-owned records/DTOs/iterators whose lifecycle is explicit and does not depend on a closed session.

3. It proves the write is idempotent under the stable identity contract instead of only succeeding once.

4. It is not the entire HTTP/client request latency; pool wait, network, transformation and serialization can sit outside it.

5. The same driver ownership and resource limits become the foundation for async, concurrent, batch and high-throughput application patterns.

Summary and next step

A reliable Neo4j service owns one long-lived driver, short-lived sessions/managed transactions, parameterized Cypher, bounded result/pool behavior, explicit causal/retry rules and correlated evidence. Chapter 14 extends this foundation into asynchronous, batched and high-throughput workloads.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.