Hexagonal Boundaries: core/ and shell/¶
Principle¶
Every module separates pure business logic (core/) from infrastructure concerns (shell/).
Dependencies always point inward — shell/ depends on core/, never the reverse.
Why This Matters¶
- Testability —
core/can be tested without mocking AWS, databases, or network. Business logic tests are fast, reliable, and easy to write. - Portability — if we swap MySQL for PostgreSQL, or SQS for Kafka, only
shell/changes.core/remains untouched. - Clarity — when reading
core/, you understand the business rules. When readingshell/, you understand the infrastructure. They don't mix. - Onboarding — new developers can understand the domain by reading
core/alone without needing to understand AWS services.
The Boundary¶
core/ contains:¶
- Business entities and value objects (SQLAlchemy models define schema, but business
rules live in
core/) - Exception hierarchy that controls processing flow
- Checkpoint state machines for resumable pipelines
- Data validation schemas (Pydantic, TypedDict)
- Pure utility functions (retry decorators, context management)
- Business orchestration (
process.py)
shell/ contains:¶
- Database session management (connection pooling, read/write separation)
- AWS service wrappers (SNS publishing, SQS operations, S3 access)
- Elasticsearch client and bulk indexing
- Secrets retrieval from AWS Secrets Manager
- Domain-specific database operations (CRUD for entities)
handlers/ contains:¶
- Lambda entry points that bridge shell → core
- Event parsing and routing
- Exception-based flow control (which exceptions trigger which SQS behavior)
The Rule¶
core/ NEVER imports from shell/
core/ NEVER imports boto3, botocore, or any AWS SDK
core/ NEVER makes network calls, file I/O, or database queries directly
shell/ passes data INTO core/ via function arguments
shell/ creates sessions and passes them to core/ methods
shell/ handles all infrastructure lifecycle (connections, retries, cleanup)
How Sessions Cross the Boundary¶
The one pragmatic exception: SQLAlchemy sessions are created in shell/ and passed
to core/ methods as arguments. This keeps core/ testable (mock the session) while
allowing it to express database operations through the ORM.
# shell/ creates the session
with writer_session(npcase_id=case_id) as session:
# core/ receives it as a parameter
result = create_exhibit_data(session, import_attributes)
This is the agreed boundary contract. core/ never creates its own sessions.
Ask the Architecture
×
Ask questions about Nextpoint architecture, patterns, rules, or any module. Powered by Claude Opus 4.6.