Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge develop into master #24

Merged
merged 10 commits into from
Dec 31, 2023
39 changes: 23 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,23 @@ Defines a common event store Java interface and provides some adapters (like for
## Status
![Warning](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/warning.gif) **This is work in progress** ![Warning](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/warning.gif)

| Module | Description | Status | Comment |
|:-------|:------------|--------|:--------|
| [esc-api](api) | Defines the event store commons API. | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~92% |
| [esc-http](eshttp) | HTTP adapter for Greg Young's [event store](https://www.geteventstore.com/)| ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~66% |
| [esc-esjc](esjc) | [Event Store Java Client](https://github.com/msemys/esjc) adapter for Greg Young's [event store](https://www.geteventstore.com/)| ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~80% |
| [esc-grpc](grpc) | [Event Store DB Client](https://github.com/EventStore/EventStoreDB-Client-Java) adapter for Greg Young's [event store](https://www.geteventstore.com/)| ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~80% |
| [esc-jpa](jpa) | JPA adapter | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~59% |
| [esc-mem](mem) | In-memory implementation | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~60% |
| [esc-spi](spi) | Helper classes for adapters and implementations | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~67% |
| [esc-test](test) | Cucumber tests for adapters and implementations | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Subscriptions not tested yet |
| Module | Description | Status | Comment |
|:------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------|--------|:-----------------------------|
| [esc-api](api) | Defines the event store commons API. | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~92% |
| [esc-http-admin](admin) | HTTP projection admin adapter for Greg Young's [event store](https://www.geteventstore.com/) | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~80% |
| [esc-grpc](grpc) | [Event Store DB Client](https://github.com/EventStore/EventStoreDB-Client-Java) adapter for Greg Young's [event store](https://www.geteventstore.com/) | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~80% |
| [esc-jpa](jpa) | JPA adapter | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~59% |
| [esc-mem](mem) | In-memory implementation | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~60% |
| [esc-spi](spi) | Helper classes for adapters and implementations | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Test coverage ~67% |
| [esc-test](test) | Cucumber tests for adapters and implementations | ![OK](https://raw.githubusercontent.com/fuinorg/event-store-commons/master/doc/ok.png) | Subscriptions not tested yet |

Deprecated modules:

| Module | Description | Comment |
|:-------------------|:-------------------------------------------------------------|:------------------------------------------------------|
| [esc-http](eshttp) | HTTP adapter for Greg Young's event store | No longer supported by event store (use GRPC instead) |
| [esc-esjc](esjc) | Event Store Java Client adapter for Greg Young's event store | No longer supported by event store (use GRPC instead) |


## Architecture
![Layers](https://raw.github.com/fuinorg/event-store-commons/master/doc/event-store-commons.png)
Expand All @@ -42,12 +49,12 @@ Defines a common event store Java interface and provides some adapters (like for

### Major changes

| Version | Description |
|:---------------|:-------------------------------------------------------|
| 0.6.0 | Java 17 / Added new GRPC java client implementation |
| 0.5.0 | Namespace changed from "javax" to "jakarta" |
| 0.4.0 | Java 11 |
| 0.3.1 | Type of the event version changed from `int` to `long` |
| Version | Description |
|:--------|:-------------------------------------------------------|
| 0.6.0 | Java 17 / Added new GRPC java client implementation |
| 0.5.0 | Namespace changed from "javax" to "jakarta" |
| 0.4.0 | Java 11 |
| 0.3.1 | Type of the event version changed from `int` to `long` |

### Snapshots

Expand Down
2 changes: 1 addition & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>org.fuin.esc</groupId>
<artifactId>esc-parent</artifactId>
<version>0.6.0</version>
<version>0.7.0-SNAPSHOT</version>
</parent>

<artifactId>esc-api</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion esgrpc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>org.fuin.esc</groupId>
<artifactId>esc-parent</artifactId>
<version>0.6.0</version>
<version>0.7.0-SNAPSHOT</version>
</parent>

<artifactId>esc-esgrpc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package org.fuin.esc.esgrpc;

import com.eventstore.dbclient.CreateProjectionOptions;
import com.eventstore.dbclient.DeleteProjectionOptions;
import com.eventstore.dbclient.EventStoreDBProjectionManagementClient;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import jakarta.validation.constraints.NotNull;
import org.fuin.esc.api.*;
import org.fuin.esc.spi.ProjectionJavaScriptBuilder;
import org.fuin.esc.spi.TenantStreamId;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
* GRPC based eventstore projection admin implementation.
*/
public final class GrpcProjectionAdminEventStore implements ProjectionAdminEventStore {

private final EventStoreDBProjectionManagementClient es;

private final TenantId tenantId;

/**
* Constructor with mandatory data.
*
* @param es Eventstore client to use.
*/
public GrpcProjectionAdminEventStore(EventStoreDBProjectionManagementClient es) {
this(es, null);
}

/**
* Constructor with all data.
*
* @param es Eventstore client to use.
* @param tenantId Tenant ID or {@literal null}.
*/
public GrpcProjectionAdminEventStore(EventStoreDBProjectionManagementClient es, TenantId tenantId) {
Contract.requireArgNotNull("es", es);
this.es = es;
this.tenantId = tenantId;
}

@Override
public ProjectionAdminEventStore open() {
// Do nothing
return this;
}

@Override
public void close() {
// Do nothing
}

@Override
public boolean projectionExists(StreamId projectionId) {
Contract.requireArgNotNull("projectionId", projectionId);
requireProjection(projectionId);

try {
es.getStatus(new TenantStreamId(tenantId, projectionId).asString()).get();
return true;
} catch (final InterruptedException | ExecutionException ex) { // NOSONAR
if (ex.getCause() instanceof StatusRuntimeException sre) {
if (sre.getStatus().getCode().equals(Status.UNKNOWN.getCode())
&& sre.getMessage() != null && sre.getMessage().contains("NotFound")) {
return false;
}
}
throw new RuntimeException("Error waiting for getStatus(..) result", ex);
}

}

@Override
public void enableProjection(StreamId projectionId) throws StreamNotFoundException {
Contract.requireArgNotNull("projectionId", projectionId);
requireProjection(projectionId);

try {
es.enable(new TenantStreamId(tenantId, projectionId).asString()).get();
} catch (final InterruptedException | ExecutionException ex) { // NOSONAR
throw new RuntimeException("Error waiting for enable(..) result", ex);
}
}

@Override
public void disableProjection(StreamId projectionId) throws StreamNotFoundException {
Contract.requireArgNotNull("projectionId", projectionId);
requireProjection(projectionId);

try {
es.disable(new TenantStreamId(tenantId, projectionId).asString()).get();
} catch (final InterruptedException | ExecutionException ex) { // NOSONAR
throw new RuntimeException("Error waiting for disable(..) result", ex);
}
}

@Override
public void createProjection(StreamId projectionId, boolean enable, @NotNull TypeName... eventType) throws StreamAlreadyExistsException {
createProjection(projectionId, enable, Arrays.asList(eventType));
}

@Override
public void createProjection(StreamId projectionId, boolean enable, List<TypeName> eventTypes) throws StreamAlreadyExistsException {
Contract.requireArgNotNull("projectionId", projectionId);
requireProjection(projectionId);

final TenantStreamId pid = new TenantStreamId(tenantId, projectionId);
final String javascript = new ProjectionJavaScriptBuilder(pid).types(eventTypes).build();
try {
es.create(pid.asString(), javascript, CreateProjectionOptions.get().emitEnabled(false).trackEmittedStreams(true)).get();
} catch (final InterruptedException | ExecutionException ex) { // NOSONAR
throw new RuntimeException("Error waiting for create(..) result", ex);
}
if (enable) {
enableProjection(pid);
} else {
// Workaround for https://github.com/EventStore/EventStoreDB-Client-Java/issues/259 (not a perfect one...)
disableProjection(pid);
}
}

@Override
public void deleteProjection(StreamId projectionId) throws StreamNotFoundException {
Contract.requireArgNotNull("projectionId", projectionId);
requireProjection(projectionId);

disableProjection(projectionId);

final TenantStreamId pid = new TenantStreamId(tenantId, projectionId);
try {
es.delete(pid.asString(), DeleteProjectionOptions.get().deleteCheckpointStream().deleteStateStream().deleteEmittedStreams()).get();
} catch (final InterruptedException | ExecutionException ex) { // NOSONAR
throw new RuntimeException("Error waiting for delete(..) result", ex);
}

}

static void requireProjection(final StreamId projectionId) {
if (!projectionId.isProjection()) {
throw new ConstraintViolationException("The stream identifier is not a projection id");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.fuin.esc.esgrpc;

import com.eventstore.dbclient.EventStoreDBClientSettings;
import com.eventstore.dbclient.EventStoreDBConnectionString;
import com.eventstore.dbclient.EventStoreDBProjectionManagementClient;
import org.fuin.esc.api.ProjectionStreamId;
import org.fuin.esc.api.TypeName;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.net.MalformedURLException;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests the {@link GrpcProjectionAdminEventStore} class.
*/
class GrpcProjectionAdminEventStoreIT {

private static EventStoreDBProjectionManagementClient client;

private GrpcProjectionAdminEventStore testee;

@BeforeAll
static void beforeAll() {
final EventStoreDBClientSettings setts = EventStoreDBConnectionString
.parseOrThrow("esdb://localhost:2113?tls=false");
client = EventStoreDBProjectionManagementClient.create(setts);
}

@BeforeEach
void beforeEach() throws MalformedURLException {
testee = new GrpcProjectionAdminEventStore(client, null);
}

@Test
void testProjectionNotExists() {
assertThat(testee.projectionExists(new ProjectionStreamId("grpc-test-not-existing"))).isFalse();
}

@Test
void testEnableDisableProjection() {

// GIVEN
final ProjectionStreamId projectionId = new ProjectionStreamId("grpc-test-disabled");
testee.createProjection(projectionId, false, new TypeName("one"), new TypeName("two"));

// WHEN
testee.enableProjection(projectionId);

// THEN
// TODO assertThat(testee.projectionEnabled()).isTrue();

}

@Test
void testCreateAndExistsProjection() {

// GIVEN
final ProjectionStreamId projectionId = new ProjectionStreamId("grpc-test-create");
assertThat(testee.projectionExists(projectionId)).isFalse();

// WHEN
testee.createProjection(projectionId, true, new TypeName("one"), new TypeName("two"));

// THEN
assertThat(testee.projectionExists(projectionId)).isTrue();

}

@Test
void testDeleteProjection() {

// GIVEN
final ProjectionStreamId projectionId = new ProjectionStreamId("grpc-test-delete");
testee.createProjection(projectionId, false, new TypeName("one"), new TypeName("two"));
assertThat(testee.projectionExists(projectionId)).isTrue();

// WHEN
testee.deleteProjection(projectionId);

// THEN
assertThat(testee.projectionExists(projectionId)).isFalse();

}

}
2 changes: 1 addition & 1 deletion eshttp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.fuin.esc</groupId>
<artifactId>esc-parent</artifactId>
<version>0.6.0</version>
<version>0.7.0-SNAPSHOT</version>
</parent>

<artifactId>esc-eshttp</artifactId>
Expand Down
52 changes: 16 additions & 36 deletions eshttp/src/main/java/org/fuin/esc/eshttp/ESHttpEventStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,7 @@
*/
package org.fuin.esc.eshttp;

import static org.fuin.esc.api.ExpectedVersion.ANY;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;

import jakarta.validation.constraints.NotNull;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
Expand All @@ -52,32 +37,27 @@
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.util.EntityUtils;
import org.fuin.esc.api.CommonEvent;
import org.fuin.esc.api.EventNotFoundException;
import org.fuin.esc.api.ExpectedVersion;
import org.fuin.esc.api.SimpleStreamId;
import org.fuin.esc.api.StreamAlreadyExistsException;
import org.fuin.esc.api.StreamDeletedException;
import org.fuin.esc.api.StreamEventsSlice;
import org.fuin.esc.api.StreamId;
import org.fuin.esc.api.StreamNotFoundException;
import org.fuin.esc.api.StreamReadOnlyException;
import org.fuin.esc.api.StreamState;
import org.fuin.esc.api.TenantId;
import org.fuin.esc.api.TypeName;
import org.fuin.esc.api.WrongExpectedVersionException;
import org.fuin.esc.spi.AbstractReadableEventStore;
import org.fuin.esc.spi.DeserializerRegistry;
import org.fuin.esc.spi.EnhancedMimeType;
import org.fuin.esc.spi.EscSpiUtils;
import org.fuin.esc.spi.SerDeserializerRegistry;
import org.fuin.esc.spi.SerializerRegistry;
import org.fuin.esc.spi.TenantStreamId;
import org.fuin.esc.api.*;
import org.fuin.esc.spi.*;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;

import static org.fuin.esc.api.ExpectedVersion.ANY;

/**
* Implementation that connects to the http://www.geteventstore.com via HTTP API.
*/
Expand Down
Loading