Loading...
Loading...
[Pragmatic DDD Architecture] Guide for creating DDD Repositories (Interfaces and Infrastructure). Use when creating repository contracts or implementing them using Drizzle ORM, Zod, and Postgres. Enforces completely typed transactions with Drizzle Transaction types (no 'unknown'), Result returns for Railway-oriented programming via neverthrow, and mapping pg node errors to domain errors. Fits our docker-compose / drizzle-kit standard testing workflow.
npx skill4agent add leif-sync/pragmatic-ddd repositoriesdrizzle-ormnode-postgresdrizzle-kitneverthrowuse-casessrc/<context>/domain/interfaces/entityRepository.tsEntityRepositorysrc/<context>/infrastructure/postgresEntityRepository.tsreferences/examples/repository-interface-example.mdabstract classinterfaceResult<T, Errors>tx?: Transaction// MUST: Accept VOs, explicit Error typing
export abstract class WorkspaceRepository {
// MUST: Require scope context (userId)
abstract list(params: {
userId: UUID;
itemsPerPage: PositiveInteger;
page: PositiveInteger;
}): Promise<Result<Workspace[], RepositoryError>>;
// MUST: Require scope context (userId)
// Method Overloading: Repositories MAY expose overloaded signatures for different
// authorization levels or unique constraint checks.
abstract exists(params: { workspaceId: UUID }): Promise<Result<boolean, RepositoryError>>;
abstract exists(params: { userId: UUID; workspaceId: UUID }): Promise<Result<boolean, RepositoryError>>;
// MUST: Require scope context (userId)
abstract create(params: {
tx?: Transaction; // Strongly typed
userId: UUID;
workspace: Workspace;
}): Promise<Result<void, RepositoryError | UserNotFoundError>;
}userIdworkspaceIdfolderIdWHEREAND folderId = ?UNIQUEFOREIGN KEY<Dependency>NotFoundError<Field>AlreadyInUseErrorRepositoryErrorcount()export abstract class WorkspaceRepository {
// Admin scope: no user restriction
abstract exists(params: { workspaceId: UUID }): Promise<Result<boolean, RepositoryError>>;
// User scope: isolated by userId
abstract exists(params: {
workspaceId: UUID;
userId: UUID;
}): Promise<Result<boolean, RepositoryError>>;
}references/examples/repository-implementation-example.mdneverthrowerror.codeValueObjectsvo.getValue().insert().update()wheredrizzle-ormexecutorNodePgDatabasetypeof dbconst executor = p.tx ?? this.dbexecutor.insert()Transaction@/shared/domain/types/withTransactionanycatch (error)unknownDrizzleQueryErrorDatabaseErrorcodeconstrainterror.cause.codePG_ERROR_CODES@/shared/infrastructure/drizzle-postgres/pgErrorCodesPG_ERROR_CODES.UNIQUE_VIOLATION...AlreadyInUseErrorPG_ERROR_CODES.FOREIGN_KEY_VIOLATION...NotFoundErrorerror.cause.constrainterror.cause.constraintFK_WORKSPACES_USERimport { DrizzleQueryError } from "drizzle-orm";
import { DatabaseError } from "pg";
import { PG_ERROR_CODES } from "@/shared/infrastructure/drizzle-postgres/pgErrorCodes";
import { FK_WORKSPACES_USER } from "@/shared/infrastructure/drizzle-postgres/schema";
// ...
async create({ tx, userId, workspace }: CreateParams): Promise<Result<void, CreateErrors>> {
const executor = tx ?? this.db;
try {
await executor.insert(workspaces).values({
id: workspace.getId().getValue(),
name: workspace.getName().getValue(),
});
return ok(undefined);
} catch (error) {
if (error instanceof DrizzleQueryError && error.cause instanceof DatabaseError) {
if (
error.cause.code === PG_ERROR_CODES.FOREIGN_KEY_VIOLATION &&
error.cause.constraint === FK_WORKSPACES_USER
) {
return err(new UserNotFoundError({ id: userId, cause: error }));
}
}
return err(new RepositoryError(error as Error));
}
}drizzle-kitzodtestcontainersdockertesting