Query

The Readmodel is a prepared view on data that is projected from Events. The data schema of a Readmodel is optimized for the need of the consumer that queries the Readmodel.

  • Command API and Command Policy use cases query Readmodels to make business decisions based on the result data.
  • Projection use cases query Readmodels to enrich the projection process with additional data.
  • Query API use cases query Readmodels to provide data to an API endpoint.

Plan Readmodel Query

Query Result Amount

Determines the amount of entities returned by the query:

  • one: Returns a single entity or null.
  • many: Returns an array of entities.

Query Name

The Name is used to name the Query and Query Handler. It also sets the folder and infrastructure name for the Usecase.

For more details, see Naming.

Conditions

1
Start adding condition
  • Click plus to add a condition
2
Select a Value Target
  • The Value Target is a property of the Readmodel schema.
  • The Value Target provides the property name the projection WHERE condition.
3
Select a Value Source
  • The Value Source is a property of the Event or a Source Readmodel.
  • The Value Source provides the property value the projection WHERE condition.

Your screen should look like that

Implement Readmodel Query

Query

A Query is a Data Transfer Object (DTO) that contains the information needed to fetch data from the system. It acts as a request for data retrieval and is passed to the QueryHandler.

A Query can be found at: Task/src/useCases/read/TaskOverview/application/GetTasksByAssigneeIdQuery.ts.

1import { isType } from "is-what";
2import { validate } from "uuid";
3import { OverwriteProtectionBody, ValidationError } from "@codebricks/typebricks";
4
5export interface GetTasksByAssigneeIdQueryProperties {
6 assigneeId: string;
7}
8
9export class GetTasksByAssigneeIdQuery {
10 constructor(readonly properties: GetTasksByAssigneeIdQueryProperties) {
11 this.validate();
12 }
13
14 @OverwriteProtectionBody(false)
15 validate(): void {
16 if (!validate(this.properties.assigneeId)) {
17 throw new ValidationError('assigneeId is invalid');
18 }
19 }
20}

The GetTasksByAssigneeIdQuery class is responsible for holding query parameters and ensuring their validity through the validate method.

Query Handler

The QueryHandler processes the Query and interacts with the repository to fetch the required data. It converts the Query into a request that the repository can handle and returns the results.

The QueryHandler can be found at: Task/src/useCases/read/TaskOverview/application/GetTasksByAssigneeIdQueryHandler.ts.

1import { OverwriteProtectionBody } from "@codebricks/typebricks";
2import { TaskOverview } from "shared/application/readmodels/TaskOverview";
3import { TaskOverviewRepository } from "../infrastructure/TaskOverviewRepository";
4import { GetTasksByAssigneeIdQuery } from "./GetTasksByAssigneeIdQuery";
5
6export class GetTasksByAssigneeIdQueryHandler {
7 constructor(readonly repository: TaskOverviewRepository = new TaskOverviewRepository()) {
8 }
9
10 @OverwriteProtectionBody(false)
11 async handle(query: GetTasksByAssigneeIdQuery): Promise<TaskOverview[]> {
12 const result: TaskOverview[] = await this.repository.getTasksByAssigneeId(
13 query.properties.assigneeId,
14 );
15 return result;
16 }
17}

The GetTasksByAssigneeIdQueryHandler class is responsible for:

  • Receiving the GetTasksByAssigneeIdQuery.
  • Using the repository to fetch the data.
  • Returning the results to the caller.

Best Practices

  • DTO Usage: Use Query classes as DTOs to encapsulate query parameters and validation logic.
  • Separation of Concerns: Keep Query and QueryHandler responsibilities distinct to maintain clarity and manageability.
  • Error Handling: Implement robust validation and error handling within Queries and QueryHandlers to ensure data integrity and user-friendly error responses.

Query Repository

In the CQRS architecture, the Query Repository is responsible for accessing and retrieving data from the database. It is used to perform read operations, ensuring that queries return the necessary data for processing.

An implementation of a Query Repository can be found at: Task/src/useCases/read/TaskOverview/infrastructure/TaskOverviewRepository.ts.

1import { OverwriteProtectionBody } from "@codebricks/typebricks";
2import { TaskOverview } from "shared/application/readmodels/TaskOverview";
3import { TaskOverviewEntity } from "shared/infrastructure/persistence/readmodel/TaskOverviewEntity";
4import { AppDataSource } from "shared/infrastructure/persistence/AppDataSource";
5
6export class TaskOverviewRepository {
7 @OverwriteProtectionBody(false)
8 async getTasksByAssigneeId(assigneeId: string): Promise<TaskOverview[]> {
9 return await AppDataSource.manager.find(TaskOverviewEntity, {
10 where: {
11 assigneeId: assigneeId,
12 }
13 });
14 }
15}
  • ActiveTaskOverviewRepository Class: This class acts as the Query Repository for the TaskOverviewEntity. It is responsible for fetching data from the database.

  • getAll Method:

    • This method retrieves all instances of ActiveTaskOverviewEntity from the database.
    • It uses TypeORM’s find method to perform the query. TypeORM is an ORM (Object-Relational Mapping) library that simplifies database interactions in TypeScript applications.
  • TypeORM Notation: The method find is part of TypeORM’s API for querying the database. It allows specifying conditions to fetch records from the database.

Best Practices

  • Repository Pattern: Use repositories to encapsulate data access logic and isolate it from the rest of the application. This pattern helps in managing data retrieval and manipulation efficiently.

  • Error Handling: Implement proper error handling in your repository methods to manage database errors and provide meaningful feedback.


© 2024 Codebricks | All rights reserved.