Skip to content

Query Handler

The QueryHandler interface in comby defines the structure for handling domain-specific queries. It ensures that each query handler is uniquely identifiable, supports specific queries, and provides the necessary logic for processing them.

In comby an QueryHandler is defined as an interface as follows:

go
type DomainQueryHandler struct {
	DomainQryPath          string                 // Technical path of the domain query
	DomainQryName          string                 // Name of the domain query
	DomainQry              DomainQry              // Runtime instance of the domain query
	DomainQueryHandlerFunc DomainQueryHandlerFunc // Function to handle the domain query
}

type QueryHandler interface {
	// Identifier is used to uniquely identify the handler (method inherited from Identifier interface).
	Identifier

	// GetDomainQueryHandler returns a DomainQueryHandler for the specified DomainQry.
	GetDomainQueryHandler(domainQry DomainQry) *DomainQueryHandler

	// GetDomainQueries returns a slice of supported domain queries.
	GetDomainQueries() []DomainQry
}

The QueryHandler inherits from the Identifier interface, which assigns a unique domain and name to each handler, ensuring clear identification within the system.

The GetDomainQueryHandler method allows the retrieval of a DomainQueryHandler, which represents the logic for handling a specific DomainQry. This enables the framework to dynamically invoke the appropriate query-processing logic based on the type of the domain query.

The GetDomainQueries method provides a list of all domain queries that the handler supports, offering a clear overview of its capabilities. This information is essential for routing queries to the appropriate handler and ensuring compatibility within the comby framework.

Base Query Handler

The BaseQueryHandler is a foundational implementation of the QueryHandler interface in comby, designed to manage domain queries and their associated handler functions. It provides a robust structure for handling queries within a domain, ensuring clear routing and processing of query logic. The structure of BaseQueryHandler is defined as follows:

go
type BaseQueryHandler struct {
	*BaseIdentifier                                    // Embeds base identifier
	domainQryMap    map[string]*DomainQueryHandler // Registry of domain query handlers
}

At its core, the BaseQueryHandler embeds the BaseIdentifier, assigning a unique domain and name to each query handler instance for identification within the system. It maintains a registry, domainQryMap, which maps domain queries to their metadata and handling logic. Each entry in this registry is represented by the DomainQueryHandler struct, which includes details such as the query's type path, name, runtime instance, and associated handler function.

The NewBaseQueryHandler function initializes a new BaseQueryHandler instance, setting default values for its domain and generating a unique name. This ensures the handler is properly configured and ready for use.

The GetDomainQueryHandler method retrieves the handler function for a specific domain query by matching its type path. If no matching handler is found, the method returns nil. The GetDomainQueries method returns a list of all registered domain queries that the handler can process, providing an overview of its supported operations.

The addDomainQueryHandler method is used to register new query handlers. It ensures the provided DomainQry and DomainQueryHandlerFunc are valid and prevents duplicate handlers for the same query type. If successful, the new handler is added to the registry, making it available for query processing.

User-Defined Query Handlers

User-defined query handlers in comby are custom implementations of query processing logic tailored to a specific domain. These handlers extend the functionality of the BaseQueryHandler by integrating domain-specific logic and registering domain query handlers for managing operations on readmodels. A user-defined query handler typically includes the following components:

go
type qryHandler struct {
	*comby.BaseQueryHandler
	Readmodel *readmodel.CustomOrderReadmodel
}

func NewCustomQueryHandler(
	Readmodel *readmodel.CustomOrderReadmodel,
) *qryHandler {
	qh := &qryHandler{}
	qh.BaseQueryHandler = comby.NewBaseQueryHandler()
	qh.Domain = "Order"
	qh.Name = "CustomQueryHandler"
	qh.Readmodel = Readmodel

	// register custom domain query handlers
	comby.AddDomainQueryHandler(qh, qh.GetList)
	comby.AddDomainQueryHandler(qh, qh.GetModel)
	return qh
}

The qryHandler struct represents a user-defined query handler for the "Order" domain in comby. It extends the functionality of the BaseQueryHandler by embedding it and providing custom logic for handling domain-specific queries related to orders. Additionally, it incorporates a reference to a CustomOrderReadmodel, which serves as the data source for executing queries.

The NewCustomQueryHandler function initializes an instance of qryHandler. During initialization, it configures the base query handler by setting the domain to "Order" and assigning a custom name (CustomQueryHandler). It also associates the CustomOrderReadmodel for accessing the underlying data required for query operations.

Custom domain query handlers are registered using the comby.AddDomainQueryHandler method. In this example, the following query handlers are registered:

  • GetList: Handles queries that retrieve a list of orders.
  • GetModel: Handles queries that retrieve a specific order model.

Add Domain Query Handler: GetModel

Any domain query handler must fullfill following signature:

go
type TypedDomainQueryHandlerFunc[T DomainQry, R QueryResponse] func(ctx context.Context, qry Query, domainQry T) (R, error)

The GetModel query handler is part of the custom query handler for the "Order" domain in comby. It processes queries to retrieve a specific order model, leveraging the associated CustomOrderReadmodel for data access and returning a structured response.

go
type CustomModelRequest struct {
	OrderUuid string `json:"orderUuid"`
}

type CustomModelResponse struct {
	Item *readmodel.CustomOrderModel `json:"order,omitempty"`
}

func (qh *qryHandler) GetModel(ctx context.Context, qry comby.Query, domainQry *CustomModelRequest) (*CustomModelResponse, error) {
	var err error

	// validate uuid
	err = utils.ValidateUuid(domainQry.OrderUuid)
	if err != nil {
		return nil, fmt.Errorf("%s failed - orderUuid is invalid", utils.GetTypeName(domainQry))
	}

	// retrieve item read model
	model, err := qh.Readmodel.GetModel(domainQry.OrderUuid)
	if err != nil {
		return nil, err
	}

	// generate response
	response := &CustomModelResponse{}
	response.Item = model

	// return response
	return response, nil
}

This handler is designed to handle queries encapsulated in the CustomModelRequest struct, which includes the OrderUuid to identify the specific order being queried. The response is returned as a CustomModelResponse, containing the retrieved order model.

The GetModel method begins by validating the OrderUuid provided in the query using utils.ValidateUuid. If the UUID is invalid, an error is returned, ensuring the integrity of the input data. Next, the method interacts with the Readmodel to fetch the order model corresponding to the validated OrderUuid. Any errors during this retrieval process are propagated back to the caller.

Upon successfully retrieving the model, the method constructs a CustomModelResponse object, embedding the retrieved data within the Item field. The response is then returned to the caller, completing the query execution.