Introduction
In this two-part guide, we’ll build a Store to handle CRUD (Create, Read, Update, Delete) operations for the Trails app. This first part focuses on defining a flexible data model and the operations required to accommodate complex queries and mutations.Prerequisites:
- Complete the Quickstart.
- Have an understanding of the core Store concepts.
The code for this example is available in the
Trails repository.
Defining the Data Model
Creating a Generalized Model Interface
To create a flexible and reusable Store, we’ll start by defining a generalized Model interface. This interface represents any data type we wish to store and forms the foundation of our data model.
Model interfaces uses type parameters K, P, and E to represent the Key, Properties, and Edges of the model, respectively. This design allows us to define models with varying structures while maintaining type safety.
Implementing the Model Interface in Post
With the Model interface in place, we can now refactor our Post class to implement this interface. This enables us to define operations on different aspects of a Post, such as its key, properties, and edges.
Implementing the
Model interface in Post allows us to define operations on various Post models. For instance:- When creating a new
Post, we have only theProperties(noKeyorEdges). - When updating a
Post, we have both theKeyand theProperties. - When fetching a feed of
Posts, we have a list ofPost.Node. - When fetching a single
Post, we have aPost.Composite.
Post class now aligns with the Model interface, allowing us to leverage the type hierarchy for different operations.
Defining Operations
Creating the Operation Sealed Class
To represent different types of operations (queries and mutations), we’ll define a generalized Operation sealed class. This class serves as the base for all specific operation types in our Store.
Operation class is parameterized with the types K, P, E, and N, corresponding to the Model components. The Operation class has two subclasses, Query and Mutation. Query represents read operations, while Mutation represents write operations.
Defining Create Operations
Under the Mutation class, we can define the Create operations that allow us to insert new data into the Store.
Create operations, we have InsertOne and InsertMany, which accept Properties or a list of Properties as input. Notice that we use Nothing for types we don’t have yet, such as the Key for new entries.
Defining Read Operations
Let’s define a DataSources sealed class to represent the different sources of data we may want to query.
DataSources class allows us to specify whether to fetch data from memory, disk, or remote sources.
Next, we can define the Query operations within the Operation class. Each query
operation specifies the DataSources to use.
Query operations include:
FindOneandFindManyfor fetching specific items by key(s).FindAllfor fetching all items.ObserveOneandObserveManyfor observing changes to specific items.
Defining Update Operations
Under the Mutation class, we can define the Update operations for modifying existing data.
Update operations include:
UpdateOneandUpdateManyfor updating existing items.UpsertOneandUpsertManyfor inserting or updating an item.
Defining Delete Operations
Finally, we can define Delete operations under the Mutation class.
Delete operations include:
DeleteOneandDeleteManyfor deleting specific items by key(s).DeleteAllfor deleting all items.
Key of the items to be deleted.
Defining Output Types
Because our operations can return either a singleModel or a collection of Models, we need to define an output type that can represent both cases.
We’ll define an Output sealed class to represent the results of our operations.
Output class has two subclasses:
Single, which wraps a singleModelitem.Collection, which wraps a list ofModelitems.
Store to handle both single-item and multiple-item results in a type-safe manner.
Conclusion
With all the components in place, we can now redefine ourPostStore to use the new Operation and Output types.
Fetcher, SourceOfTruth, Updater, and Bookkeeper to support these operations.