Overview
A friendly, step-by-step tour of our infrastructure: start with a tiny model, add shared columns, then grow into a full repository with Drizzle and Effect — explained in plain language.
Infrastructure Evolution
- Begin with a tiny
hoststable containing only the most essential columns. - Keep it simple first, then evolve the model as needs grow.
Almost every table needs create/updatetimestamps and unique IDs.
- Instead of copying the same code everywhere, we'll create a reusable "blueprint" (
sharedColumns). - This keeps our data consistent and our code clean!
Now we plug our helper into the hosts table.
- We spread
...SharedColumnsto getid,createdAt, andupdatedAt, and then add auserIdcolumn that references the users table. - This step shows how to reuse shared columns and wire up a foreign key (userId) in Drizzle.
The real domain isn’t just an ID and email – hosts have profile data.
- Here we expand the hosts table with optional details: date of birth, phone number, address fields, and social links.
With the table done, we switch to the repository.
- The key idea: before writing a domain entity to the database, we call
toDbSerializedto flatten any nested value objects into simple columns.
Let’s add the first write method.
addserializes the entity and inserts it into the database, with clear Effect-based error handling.
Next up, let's add the update method.
- We make sure the record exists, serialize the new data, and persist the changes.
Fetch one host by user id.
- We call a reusable helper
fetchSinglethat runs the query, maps the row to a Host entity, and returns an Option.
Fetch many hosts by phone number.
- We call a reusable helper
fetchMultiplethat runs the query, maps the rows to Host entities, and returns a list.
Not every host has a fully filled profile.
- This step builds a query that returns only “complete profiles” and applies pagination.
Here we combine text search, filters, and pagination into one flexible search function.
- The repository builds a dynamic query, executes it, and returns a paginated list of Host entities.
Finally, delete a host by id: check if it exists, then remove it.
Code Organization
The infrastructure layer is organized around concrete implementations of persistence and integration concerns: repositories, database setup (schema & tables), and configuration.
Key Lessons
-
Shared Column Patterns: Use utility functions like
UuidCol()andSharedColumnsto ensure consistency across all tables and reduce duplication of common fields like ID, timestamps. -
Domain-to-DB Serialization: Flatten nested domain value objects (like
addressandsocialLinks) into individual table columns usingtoDbSerialized()for optimal database storage. -
Effect-Based Repository Pattern: Compose operations using Effect's
pipe,E.tryPromise, andE.flatMapfor type-safe error handling and predictable data flow throughout the repository layer. -
Helper Method Composition: Break complex operations into focused helpers (
executeQuery,createEntity,fetchSingle,fetchMultiple) that can be composed for different query patterns. -
Entity Reconstruction: Transform flat database rows back into rich domain entities by reconstructing nested value objects during the
createEntityprocess. -
Validation at Boundaries: Use
ensureExists()for write operations and proper error types (HostNotFoundError,HostUpdateError) to maintain data integrity and clear error semantics.