{"id":28680717,"url":"https://github.com/matsientst/dewdrop","last_synced_at":"2025-06-14T02:03:44.471Z","repository":{"id":45428964,"uuid":"492918446","full_name":"matsientst/dewdrop","owner":"matsientst","description":"Dewdrop is a simple, fast, and powerful java based event sourcing framework. ","archived":false,"fork":false,"pushed_at":"2025-06-03T16:54:32.000Z","size":1078,"stargazers_count":44,"open_issues_count":0,"forks_count":6,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-06-04T02:15:18.567Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://dewdrop.events/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/matsientst.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-05-16T16:38:10.000Z","updated_at":"2025-06-03T16:54:35.000Z","dependencies_parsed_at":"2024-05-03T03:02:55.874Z","dependency_job_id":"7f3da9c7-752a-4d29-9087-705291d052a3","html_url":"https://github.com/matsientst/dewdrop","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/matsientst/dewdrop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matsientst%2Fdewdrop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matsientst%2Fdewdrop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matsientst%2Fdewdrop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matsientst%2Fdewdrop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matsientst","download_url":"https://codeload.github.com/matsientst/dewdrop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matsientst%2Fdewdrop/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259747197,"owners_count":22905308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-14T02:01:11.615Z","updated_at":"2025-06-14T02:03:44.464Z","avatar_url":"https://github.com/matsientst.png","language":"Java","funding_links":[],"categories":["开发框架"],"sub_categories":[],"readme":"# Dewdrop\nDewdrop is a simple, fast, and powerful java based event sourcing framework. The idea of Dewdrop is to make it easy to build an event driven system easily and quickly by pushing all the complex reading, writing and marshalling down deep into the framework allowing your team to focus on building out the business logic in terms of AggregateRoot behavior, Query logic, and ReadModel composition. \n\nIf you're new to event sourcing we highly suggest understanding the main concepts around the ideas of CQRS, DDD, Event Storming and Event Modeling.\n\nDewdrop is in its early stages of development and is not yet production ready. If you're interested in using it in a production environment, please reach out. We are looking to continue testing and devloping until it's ready for prime time. If you're only interested in using Dewdrop as a tool to understand event sourcing or for a simple project, then Dewdrop is perfect for you. We'd also love to hear your feedback!\n\nCurrently, Dewdrop only supports using [EventStore](https://www.eventstore.com/) as its backing store. We will be expanding it to use other data stores and would love your feedback on what is most interesting to you in terms of next steps.\n\nRequirements:\n* Java 11 or later\n* EventStore\n\n\n## Getting Started\nFirst, you need to make sure you're running EventStore locally. To do this you can go download the [EventStore](https://www.eventstore.com/downloads) client and run it. Or, you can run a docker instance which is included in the repository.\nTo start the docker instance, run the following command in the dewdrop directory:\n\n`docker-compose up -d`\n\nWe are also assuming that most people getting going with the project are running it inside a dependency injected framework like Spring Boot. If this is the case you need to create a class that wraps your DI application context. For the case of Spring Boot you'd create a class that implements `DependencyInjectionAdapter` and expose it as a bean.\n\n```java\npublic class DewdropDependencyInjection implements DependencyInjectionAdapter {\n        private ApplicationContext applicationContext;\n        public DewdropDependencyInjection(ApplicationContext applicationContext) {\n                this.applicationContext = applicationContext;\n        }\n\n        @Override\n        public \u003cT\u003e T getBean(Class\u003c?\u003e clazz) {\n            return (T) applicationContext.getBean(clazz);\n        }\n}\n```\nThis lets Dewdrop know that it should use the application context to get the spring managed beans it needs.\n\nThe next step is to create a `DewdropConfiguration` class that will be used to configure the Dewdrop framework.\n\n```java\nimport java.beans.BeanProperty;\n\npublic class DewdropConfiguration {\n    @Autowired\n    ApplicationContext applicationContext;\n    \n    @Bean \n    public DewdropDependencyInjection dependencyInjection() {\n        return new DewdropDependencyInjection(applicationContext);\n    }\n    \n    @Bean\n    public DewdropProperties dewdropProperties() {\n        return DewdropProperties.builder()\n            .packageToScan(\"events.dewdrop\")\n            .packageToExclude(\"events.dewdrop.fixture.customized\")\n            .connectionString(\"esdb://localhost:2113?tls=false\")\n            .create();\n    }\n    \n    @Bean \n    public Dewdrop dewdrop() {\n        return DewdropSettings.builder()\n            .properties(dewdropProperties())\n            .dependencyInjectionAdapter(dependencyInjection())\n            .create()\n            .start();\n    }\n}\n``` \nAnd that is it! You can now run the application and it will start up the Dewdrop framework.\n\n## Core Concepts of Dewdrop\nDewdrop follows a few architectural principles religiously to make it easy to build a system that is event driven. The following are the core concepts of Dewdrop:\n* DDD - Domain Driven Design\n* CQRS - Command Query Responsibility Separation\n* Event sourcing - Event driven architecture\n\nIf you are unfamiliar with these principles, we highly suggest diving deeper and understanding them before jumping into Dewdrop. It will make much more sense when you're familiar with these concepts.\n\n\n## Using Dewdrop\nThe easiest way to use Dewdrop is to use the `Dewdrop` class which encapsulates the entire framework. The crazy simple API is:\n\n`dewdrop.executeCommand(command)`\n\n`dewdrop.executeSubsequentCommand(command, earlierCommand)`\n\n`dewdrop.executeQuery(query)`\n\nThe framework will take care of the rest.\n\n### Command side - dewdrop.executeCommand(command)\nEric Evans’ book Domain Driven Design describes an abstraction called “aggregate”:\n\n\u003e “An aggregate is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each aggregate has a root and a boundary.”\n\nDewdrop follows this idea assuming that to mutate state we create an AggregateRoot that is used as a unit of persistent to group together related events. For example, if you have a `User` aggregate you would create a `UserAggregate` that then contains all the command handling and event handling for mutating state.\n\nTo create an AggregateRoot in Dewdrop, you need to create a class and then decorate it with the `@AggregateRoot` annotation. This tells the framework that this is the aggregateRoot object where the logic for state mutation exists. This is an example of a simple AggregateRoot: \n\n```java\n@Aggregate\npublic class DewdropAccountAggregate {\n    @AggregateId\n    UUID accountId;\n    String name;\n    BigDecimal balance = BigDecimal.ZERO;\n\n    public DewdropAccountAggregate() {}\n\n    @CommandHandler\n    public List\u003cDewdropAccountCreated\u003e handle(DewdropCreateAccountCommand command) {\n        if (StringUtils.isEmpty(command.getName())) { throw new IllegalArgumentException(\"Name cannot be empty\"); }\n        if (command.getAccountId() == null) { throw new IllegalArgumentException(\"AccountId cannot be empty\"); }\n        if (command.getUserId() == null) { throw new IllegalArgumentException(\"UserId cannot be empty\"); }\n\n        return new DewdropAccountCreated(command.getAccountId(), command.getName(), command.getUserId());\n    }\n\n    @EventHandler\n    public void on(DewdropAccountCreated event) {\n        this.accountId = event.getAccountId();\n        this.name = event.getName();\n    }\n}\n```\n\nTo explain what is going first we need to talk about the `@Aggregate` annotation. This is a marker annotation that tells the framework that this is an aggregateRoot. The framework will use this to get the class from DI or create a class that is then wrapped by the `AggregateRoot` class. The `AggregateRoot` class is a base class that provides the basic functionality for an aggregateRoot.\n\nThe key here is to understand that there is a lifecycle to modifying an AggregateRoot. The first step is to create a command to modify the AggregateRoot. This is done by creating a class that extends the `Command` class.\n\n### Command\n```java\npublic class DewdropCreateAccountCommand extends DewdropAccountCommand {\n    private String name;\n    private UUID userId;\n\n    public DewdropCreateAccountCommand(UUID accountId, String name, UUID userId) {\n        super(accountId);\n        this.name = name;\n        this.userId = userId;\n    }\n}\n```\nWhich as you can see extends `DewdropAccountCommand` which is a base class that wraps the `@AggregateRootId`.\n```java\npublic abstract class DewdropAccountCommand extends Command {\n    @AggregateId\n    private UUID accountId;\n\n    public DewdropAccountCommand(UUID accountId) {\n        super();\n        this.accountId = accountId;\n    }\n}\n```\nThe `@AggregateRootId` is the identifier of the AggregateRoot. Currently the framework only supports UUID as an identifier. This id is used to read/write to the appropriate stream for the AggregateRoot. For example, if you have an AggregateRoot like the one listed above `DewdropAccountAggregate`, and a UUID of `e4cb9a9c-308f-4fb7-a3df-8ada95749c7e`, the framework would save this to a stream called `DewdropAccountAggregate-e4cb9a9c-308f-4fb7-a3df-8ada95749c7e`. And in turn, for each command that is run on this AggregateRoot after it's creation, you'll need to pass in the appropriate UUID as the `@AggregateRootId` for the framework to know which AggregateRoot to load.\n\nIn this situation we have created an abstract base class called `DewdropAccountCommand` to hold this value. And all commands that impact this AggregateRoot should extend this class. We suggest you create you're own version of this base command class for each of your AggregateRoots. \n\nThe `Command` class is a base class that is used to create commands. All the command classes MUST extend this object for the framework to function correctly. \n\nThis relates directly to the CQRS pattern. The `Command` class is used to create a command that is then sent to the aggregateRoot. The aggregateRoot then modifies the state of the aggregateRoot and then publishes an event that is then sent to the event store.\n\nHere's an example test:\n```java\n    @Test\n    private DewdropCreateAccountCommand createAccount(Dewdrop dewdrop, UUID userId) {\n        DewdropCreateAccountCommand command = new DewdropCreateAccountCommand(UUID.randomUUID(), \"test\", userId);\n        dewdrop.executeCommand(command);\n        return command;\n    }\n```\nThis test creates a command to create an account and then sends it to the Dewdrop framework. The framework then sends the command to the aggregateRoot and is then handled by the `@CommandHandler` method on the AggregateRoot that has the `DewdropCreateAccountCommand` as it's first parameter.\n```java\n    @CommandHandler\n    public List\u003cDewdropAccountCreated\u003e handle(DewdropCreateAccountCommand command) {\n        if (StringUtils.isEmpty(command.getName())) { throw new IllegalArgumentException(\"Name cannot be empty\"); }\n        if (command.getAccountId() == null) { throw new IllegalArgumentException(\"AccountId cannot be empty\"); }\n        if (command.getUserId() == null) { throw new IllegalArgumentException(\"UserId cannot be empty\"); }\n\n        return new DewdropAccountCreated(command.getAccountId(), command.getName(), command.getUserId());\n    }\n```\n\n### Event\nThe framework runs this method to generate the needed events. It then sends the events to the their applicable @EventHandler methods on the AggregateRoot. In this case:\n```java\n    @EventHandler\n    public void on(DewdropAccountCreated event) {\n        this.accountId = event.getAccountId();\n        this.name = event.getName();\n    }\n```\nMuch like the `Command` class, the `Event` classes are DTO's used to represent the state of the AggregateRoot. The `Event` classes are used to both read/write to the event store and to publish events to the event store.\n\n```java\npublic class DewdropAccountCreated extends DewdropAccountEvent {\n    private String name;\n    private UUID userId;\n\n    public DewdropAccountCreated(UUID accountId, String name, UUID userId) {\n        super(accountId);\n        this.name = name;\n        this.userId = userId;\n    }\n}\n```\nAs you can see, this class also extends a base `Event` class and if we look at that:\n```java\npublic abstract class `DewdropAccountEvent` extends Event {\n    @AggregateId\n    private UUID accountId;\n\n}\n```\nLike the `DewdropAccountCommand` class, the `DewdropAccountCreated` class extends the abstract `DewdropAccountEvent` class which holds the `@AggregateRootId`. Again, this is the identifier of the AggregateRoot.\nWe highly suggest that you create base event classes for each of your AggregateRoots. This is a good way to keep your code clean and easy to maintain.\n\n### Lifecycle of the AggregateRoot\n\nTo understand better what dewdrop is doing we want to outline exactly what the framework is doing under the covers. To help illustrate this, here is the lifecycle that occurs:\n* User created code calls `dewdrop.executeCommand(command)`\n* The framework looks for the AggregateRoot that implements `@CommandHandler` with the Command object as it's first parameter\n* If the command object has an `@AggregateRootId` then the framework will load the AggregateRoot from the event store and replay all existing events to rebuild state\n* It then calls the `@CommandHandler` method on the AggregateRoot with the Command object as it's first parameter, which returns either a single or list of events (extends Event)\n* It then calls the `@EventHandler` method on the AggregateRoot with the Event object as it's first parameter to mutate the state of the AggregateRoot\n* It then persists the events to the event store\n\n## Query - dewdrop.executeQuery(query)\nThe query side relies on creation of ReadModels that are used to read from the event store. Think of a ReadModel as similar to a repository or a DAO in more traditional architectures. It is the object that defines how we read data from the event store. Now, your first intuition is to map it directly to your AggregateRoot, which can be correct at times, but more often it is not. \n\nDewdrop will read the json events from the event store, find which ReadModels are listening to those events, convert them into the event objects and then replay those event on the ReadModel.\n\nUsually, when we are reading from an event store we are looking to read from a complex interaction of events. If you start to think of what data you're looking to read as a collection of events it can help with architecting your ReadModel correctly. \n\nSo, first off, let's look at an example ReadModel in Dewdrop\n```java\n@ReadModel\n@Stream(name = \"DewdropFundsAddedToAccount\", streamType = StreamType.EVENT)\n@Stream(name = \"DewdropAccountCreated\", streamType = StreamType.EVENT)\npublic class DewdropAccountSummaryReadModel {\n    @DewdropCache\n    DewdropAccountSummary dewdropAccountSummary;\n\n    @QueryHandler\n    public DewdropAccountSummary handle(DewdropAccountSummaryQuery query) {\n\n        return dewdropAccountSummary;\n    }\n}\n``` \nHere are the important bits:\n* The `@ReadModel` annotation is used to mark the class as a ReadModel\n* The `@Stream` annotation is used to identify what streams to read from the event store\n* The `@DewdropCache` annotation is used to cache the data in the ReadModel\n* The `@QueryHandler` annotation is used to identify the method that will handle the query\n\n### @ReadModel\nAs you can see we have annotated this class with `@ReadModel`. Dewdrop on startup looks to identify ALL the classes that it can find that implement `@ReadModel` and then keeps track of them.\nWhen it finds a ReadModel it will assume that we automatically want to create that ReadModel unless we tell it not to.\n\nIf we have a situation where we don't want a ReadModel to be created and live forever, we can mark the ReadModel as `ephemeral` which tells Dewdrop to only create this class when we see a query executed for this. Consider it a lazy ReadModel, or a just in time ReadModel. If we want to have an `ephemeral` ReadModel we can then choose what that means by adding the `destroyInMinutesUnused` field to our `@ReadModel` annotation on our class.\nFor example:\n```java\n@ReadModel(ephemeral = true, destroyInMinutesUnused = ReadModel.DESTROY_IMMEDIATELY)\n@Stream(name = \"DewdropAccountAggregate\", subscribed = true)\n@Stream(name = \"DewdropUserAggregate\", subscribed = false)\npublic class DewdropAccountDetailsReadModel {\n    @DewdropCache\n    Map\u003cUUID, DewdropAccountDetails\u003e cache;\n\n    @QueryHandler\n    public Result\u003cDewdropAccountDetails\u003e handle(DewdropGetAccountByIdQuery query) {\n        DewdropAccountDetails dewdropAccountDetails = cache.get(query.getAccountId());\n        if (dewdropAccountDetails != null) { return Result.of(dewdropAccountDetails); }\n        return Result.empty();\n    }\n}\n```\nHere we've told Dewdrop that we want to create this ReadModel when we see a query executed for this ReadModel. We also told Dewdrop that we want to destroy this ReadModel immediately after it was used. This is a good way to keep your ReadModels from growing too large and eating up memory. However, this would be a bad strategy to use in a stream that has a lot of events associated with. This would become an expensive operation, and would take a long time to load if there were thousands of events.\nThe three states of `destroyInMinutesUnused` to note are:\n* `DESTROY_IMMEDIATELY` - This will destroy the ReadModel immediately after it was used.\n* `NEVER_DESTROY` - This will add the ReadModel to the cache, but it will never be destroyed until restart (just like a non-ephemeral ReadModel).\n* `n` - Just add a value here for the count of minutes you want it to live on for before destruction.\n\nDestroying a ReadModel after an hour of use is a good idea for situations where you have a UI that is being heavily used by a user and you want to keep the ReadModel alive for snappy performance. However, after that user is done we can then destroy this ReadModel and clear up memory.\n\n### @Stream\nThe `@Stream` annotation is used to identify what streams to read from the event store. You can have multiple `@Stream` annotations onto a ReadModel. To identify what stream you want to read from the Stream has these fields:\n* `name` - The name of the stream to read from\n* `streamType` - The type of the stream you want to read from which you can use the enum `StreamType` (`EVENT`, `CATEGORY` or `AGGREGATE`) to identify. `CATEGORY` is default\n* `subscribed` - Whether or not you want to subscribe to the stream. If you want to subscribe to the stream you can leave it blank since the default is `true`.\n* `direction` - Which direction you want to read - you can use the enum `Direction` (`FORWARD` or `BACKWARD`)\n\nThe name annotation should NOT be the full name that you see in your event store. In this case we use a name generator to create the name that is needed. For example \nFor the example above, you can see we passed in a name of \n\n`DewdropAccountAggregate` \n\nSince the Stream default type is `CATEGORY` we end up generating the name\n\n`$ce-DewdropAccountAggregate` \n\nIf the stream type is `EVENT` we end up generating the name\n\n`$et-DewdropAccountAggregate` \n\nBut, that wouldn't make sense since this is an AggregateRoot name, a better example would be:\n\n`$et-DewdropAccountCreated`\n\n### @DewdropCache\nThis annotation marks the field as a cache. This is used to cache the data in the ReadModel. \n```java\n @DewdropCache\n Map\u003cUUID, DewdropAccountDetails\u003e cache;\n```\nThis is one of the huge benefits of Dewdrop. Dewdrop will automatically based on the ReadModel and streams decorating the class be able to read the events from the event store and populate the ReadModel. This is a powerful and elegant way to get up and running with event sourcing without having to build a HUGE amount of plumbing.\nThese events are replayed back to the ReadModel and the ReadModel is updated. You can choose NOT to have a cache for a ReadModel by adding not adding the `@DewdropCache` annotation field. In this scenario, you should create the `@EventHandler` on the ReadModel itself.\n\n\nThere are two types of caches:\n* Map - This is a map that is used to cache the data in the ReadModel. The key is the id of the object and the value is the DTO that you want to map to (more on this later).\n* Single item - This is a single item that is used to cache the data in the ReadModel. There is no key since this becomes an accumulator. So if, for example, you want to cache the total balance of all the accounts in the system you can use this.\n\nThis is a ridiculously useful feature and is very useful. However, be careful about what is stored in cache since it is not persisted and can get quite large.\n\n#### How to use the cache\nBoth types of caches (map and single) assume that you have a DTO that you are writing state to. This should be a simple POJO, but it will need the `@EventHandler` methods decorating the methods to update state (much like the AggregateRoot).\nThe Cache DTO is made of a few annotations that dewdrop uses to know how to create and manage the cache:\n* `@DewdropCache` - This is the annotation that marks the field as a cache. This is used to cache the data in the ReadModel.\n* `@PrimaryKey` - This is the annotation that marks the field as the primary key. This is used to identify the primary key of the cache.\n* `@SecondaryKey` - This is the annotation that marks the field as a secondary key. This is used to identify the secondary key of the cache.\n* `@CreationEvent` - This is the annotation that marks the field as the event that marks the creation of the cache item. This is used to know to create the DTO and starting using it for that key.\n* `@EventHandler` - This is the annotation that marks the method that updates the cache. This is used to update the DTO with the event.\n\nFor example:\n```java\npublic class DewdropAccountDetails {\n    @PrimaryCacheKey\n    private UUID accountId;\n    private String name;\n    private BigDecimal balance = BigDecimal.ZERO;\n    @ForeignCacheKey\n    private UUID userId;\n    private String username;\n\n    @EventHandler\n    public void on(DewdropAccountCreated event) {\n        this.accountId = event.getAccountId();\n        this.name = event.getName();\n        this.userId = event.getUserId();\n    }\n\n    @EventHandler\n    public void on(DewdropFundsAddedToAccount event) {\n        this.balance = this.balance.add(event.getFunds());\n    }\n\n    @EventHandler\n    public void on(DewdropUserCreated userSignedup) {\n        this.username = userSignedup.getUsername();\n    }\n}\n```\nIn this example, we are reading from two streams as outlined above in the ReadModel example:\n```java\n@Stream(name = \"DewdropAccountAggregate\", subscribed = true)\n@Stream(name = \"DewdropUserAggregate\", subscribed = false)\n```\n\nThis object becomes the intersection of those two streams and replays events from each. The framework will automatically call the `@EventHandler` methods to update the state of the object based on the event received. \nIn this case, we have an event `DewdropAccountCreated` which we create a method to handel like:\n```java\n    @EventHandler\n    public void on(DewdropAccountCreated event) {\n        this.accountId = event.getAccountId();\n        this.name = event.getName();\n        this.userId = event.getUserId();\n    }\n```\n#### @EventHandler\nYou can implement any `@EventHandler` methods to handle any events you wish to from a stream. You can handle them all, or ignore the ones that are not relevant.\n\nThe events that are handled here are the same events we created when modifying the `DewdropAccountAggregate`.\n\nThis event is a special event, so let's dive a little deeper into it. \n```java\n@CreationEvent\npublic class DewdropAccountCreated extends DewdropAccountEvent {\n    private String name;\n    private UUID userId;\n\n    public DewdropAccountCreated(UUID accountId, String name, UUID userId) {\n        super(accountId);\n        this.name = name;\n        this.userId = userId;\n    }\n}\n```\n#### @CreationEvent\nThis event has the annotation of `@CreationEvent` which means that this event is the event that is used to create the cache item. Without it, Dewdrop has no idea which event is the starting point and when to create this object.\n\n#### @PrimaryCacheKey\nThe `@PrimaryCacheKey` annotation is used to identify the field that is used as the key for the cache. This relates to the `@AggregateId` annotation. The framework cache first looks for the `@PrimaryCacheKey` annotation and if it finds it, it uses that field as the key and then looks for an `@AggregateId` annotation that matches it. \n\n#### @ForeignCacheKey\nThe `@ForeignCacheKey` annotation is used to identify the field that is the foreign key used as the key for the cache. For example, In this example, we have a `@ForeignCacheKey` annotation on the `userId` field. This means that when it finds the `userId` field in the events it will relate to that field.\n\n### No Cache\nIf you decide to not use a cache, then you should skip adding the `@DewdropCache` field. When you do this, you should add the `@EventHandler` method to the ReadModel itself. If you want to persist the current state in a local datastore you can make your ReadModel a spring object and then inject your repository into the ReadModel. Then on each event you can update your repository with the new state.\n\nYou also need to add a `@StartFromPosition` decorated method that returns a long to retrieve the last version number from your cache to tell the framework where to start for that Stream. The `@StreamStartPosition` name and streamType must match the `@Stream` name and type on the same ReadModel. \n\n```java\n    @StreamStartPosition(name = \"DewdropAccountAggregate\")\n    public Long startPosition() {\n        Optional\u003cLong\u003e position = accountDetailsRepository.lastAccountVersion();\n        return position.orElse(0L);\n    }\n```\n\n\n### Querying\nTo bring this all together, to query your ReadModel all you need to do is create a query object and then call the `query` method on the ReadModel.\n\n```java\npublic class DewdropGetAccountByIdQuery {\n    private UUID accountId;\n}\n```\nThe query objects are nothing special and can be whatever type of objects you wish. They do not extend any special classes. They are just plain old POJO's.\n\nTo query a ReadModel, you just pass your query object to the `executeQuery(query)` method on dewdrop.\n\n```java\nDewdropGetAccountByIdQuery query = new DewdropGetAccountByIdQuery(accountId);\ndewdrop.executeQuery(query);\n```\nThe framework will automatically find the ReadModel associated with the Query, construct it (if needed) and then call the `@QueryHandler` method on it.\nThen, the `@QueryHandler` method will be called on the ReadModel and it will return the result of the query.\n\n```java\n    @QueryHandler\n    public Result\u003cDewdropAccountDetails\u003e handle(DewdropGetAccountByIdQuery query) {\n        DewdropAccountDetails dewdropAccountDetails = cache.get(query.getAccountId());\n        if (dewdropAccountDetails != null) { return Result.of(dewdropAccountDetails); }\n        return Result.empty();\n    }\n```\nIn this query, since there is a `@DewdropCache`, you just need to look into the cache to find the object and return it!\n\nIf you don't have cache, then you can query against a local datastore or however, you want to architect it.\n\n### Acknowledgements\nI want to thank a number of people for their help and support in creating this library.\n\nFirst, I want to thank Chris Condron at Event Store, and Josh Kempner at PerkinElmer for their expertise and guidance in creating the Dewdrop client library. A lot of the basis of Dewdrop was inspired by the [reactive-domain](https://github.com/ReactiveDomain/reactive-domain) project built in C# by both Chris and Josh.\nSecond, I want to thank Tom Friedhof, Kurtis Moffett, and Ryan Hartman for their contributions and effort in creating Dewdrop.\n\nThanks Guys!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatsientst%2Fdewdrop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatsientst%2Fdewdrop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatsientst%2Fdewdrop/lists"}