191 lines
5.2 KiB
Markdown
191 lines
5.2 KiB
Markdown
# State Stores
|
|
|
|
State Stores provide a common way to interact with different data store implementations, and allow users to opt-in to advanced capabilities using defined metadata.
|
|
|
|
## Implementing a new State Store
|
|
|
|
A compliant state store needs to implement one or more interfaces: `Store` and `TransactionalStore`, defined in the [`store.go`](store.go) file.
|
|
|
|
See the [documentation site](https://docs.dapr.io/developing-applications/building-blocks/state-management/) for examples.
|
|
|
|
## Implementing State Query API
|
|
|
|
State Store has an optional API for querying the state.
|
|
|
|
Please refer to the [documentation site](https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-state-query-api/) for API description and definition.
|
|
|
|
```go
|
|
// Querier is an interface to execute queries.
|
|
type Querier interface {
|
|
Query(req *QueryRequest) (*QueryResponse, error)
|
|
}
|
|
```
|
|
|
|
Below are the definitions of structures (including nested) for `QueryRequest` and `QueryResponse`.
|
|
|
|
```go
|
|
// QueryResponse is the request object for querying the state.
|
|
type QueryRequest struct {
|
|
Query query.Query `json:"query"`
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type Query struct {
|
|
Filters map[string]interface{} `json:"filter"`
|
|
Sort []Sorting `json:"sort"`
|
|
Page Pagination `json:"page"`
|
|
|
|
// derived from Filters
|
|
Filter Filter
|
|
}
|
|
|
|
type Sorting struct {
|
|
Key string `json:"key"`
|
|
Order string `json:"order,omitempty"`
|
|
}
|
|
|
|
type Pagination struct {
|
|
Limit int `json:"limit"`
|
|
Token string `json:"token,omitempty"`
|
|
}
|
|
|
|
// QueryResponse is the response object on querying state.
|
|
type QueryResponse struct {
|
|
Results []QueryItem `json:"results"`
|
|
Token string `json:"token,omitempty"`
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// QueryItem is an object representing a single entry in query results.
|
|
type QueryItem struct {
|
|
Key string `json:"key"`
|
|
Data []byte `json:"data"`
|
|
ETag *string `json:"etag,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
```
|
|
|
|
Upon receiving the query request, Dapr validates it and transforms into object `Query`,
|
|
which, in turn, is passed on to the state store component.
|
|
|
|
The `Query` object has a member `Filter` that implements parsing interface per component as described below.
|
|
|
|
```go
|
|
type Filter interface {
|
|
Parse(interface{}) error
|
|
}
|
|
|
|
type FilterEQ struct {
|
|
Key string
|
|
Val interface{}
|
|
}
|
|
|
|
type FilterNEQ struct {
|
|
Key string
|
|
Val interface{}
|
|
}
|
|
|
|
type FilterGT struct {
|
|
Key string
|
|
Val interface{}
|
|
}
|
|
|
|
type FilterGTE struct {
|
|
Key string
|
|
Val interface{}
|
|
}
|
|
|
|
type FilterLT struct {
|
|
Key string
|
|
Val interface{}
|
|
}
|
|
|
|
type FilterLTE struct {
|
|
Key string
|
|
Val interface{}
|
|
}
|
|
|
|
type FilterIN struct {
|
|
Key string
|
|
Vals []interface{}
|
|
}
|
|
|
|
type FilterAND struct {
|
|
Filters []Filter
|
|
}
|
|
|
|
type FilterOR struct {
|
|
Filters []Filter
|
|
}
|
|
```
|
|
|
|
To simplify the process of query translation, we leveraged [visitor design pattern](https://datacadamia.com/data/type/tree/visitor). A state store component developer would need to implement the `visit` method, and the runtime will use it to construct the native query statement.
|
|
|
|
```go
|
|
type Visitor interface {
|
|
// returns "equal" expression
|
|
VisitEQ(*FilterEQ) (string, error)
|
|
// returns "not equal" expression
|
|
VisitNEQ(*FilterNEQ) (string, error)
|
|
// returns "greater than" expression
|
|
VisitGT(*FilterGT) (string, error)
|
|
// returns "greater than equal" expression
|
|
VisitGTE(*FilterGTE) (string, error)
|
|
// returns "less than" expression
|
|
VisitLT(*FilterLT) (string, error)
|
|
// returns "less than equal" expression
|
|
VisitLTE(*FilterLTE) (string, error)
|
|
// returns "in" expression
|
|
VisitIN(*FilterIN) (string, error)
|
|
// returns "and" expression
|
|
VisitAND(*FilterAND) (string, error)
|
|
// returns "or" expression
|
|
VisitOR(*FilterOR) (string, error)
|
|
// receives concatenated filters and finalizes the native query
|
|
Finalize(string, *MidQuery) error
|
|
}
|
|
```
|
|
|
|
The Dapr runtime implements `QueryBuilder` object that takes in `Visitor` interface and constructs the native query.
|
|
|
|
```go
|
|
type QueryBuilder struct {
|
|
visitor Visitor
|
|
}
|
|
|
|
func (h *QueryBuilder) BuildQuery(mq *MidQuery) error {...}
|
|
```
|
|
|
|
The last part is to implement `Querier` interface in the component:
|
|
|
|
```go
|
|
type Querier interface {
|
|
Query(req *QueryRequest) (*QueryResponse, error)
|
|
}
|
|
```
|
|
|
|
A sample implementation might look like that:
|
|
|
|
```go
|
|
func (m *MyComponent) Query(req *state.QueryRequest) (*state.QueryResponse, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
query := &Query{} // Query implements Visitor interface
|
|
qbuilder := state.NewQueryBuilder(query)
|
|
if err := qbuilder.BuildQuery(&req.Query); err != nil {
|
|
return &state.QueryResponse{}, err
|
|
}
|
|
data, token, err := query.execute(ctx)
|
|
if err != nil {
|
|
return &state.QueryResponse{}, err
|
|
}
|
|
return &state.QueryResponse{
|
|
Results: data,
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
Some of the examples of State Query API implementation are [Redis](./redis/redis_query.go), [MongoDB](./mongodb/mongodb_query.go) and [CosmosDB](./azure/cosmosdb/cosmosdb_query.go) state store components.
|