Skip to content

Commit 52bd42c

Browse files
committed
Implement event-store additional functionality | jobs... steve jobs (C) whatthecommit.com
1 parent 8cd8b3e commit 52bd42c

File tree

12 files changed

+479
-58
lines changed

12 files changed

+479
-58
lines changed

.travis.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ jobs:
8888
- http :8080/events/00000000-0000-0000-0000-000000000000 Accept:application/json
8989
- http :8080/events
9090
- http delete :8080/events
91+
92+
- http :8080/counter
93+
- http post :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterCreated counterName=hi
94+
- http put :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterIncremented by=travis withValue=1
95+
- http put :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterIncremented withValue=2
96+
- http delete :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterSuspended reason="just because I can"
97+
- http :8080/counter/00000000-0000-0000-0000-000000000000
98+
- http :8080/counter
99+
- http delete :8080/events
100+
- http :8080/counter/00000000-0000-0000-0000-000000000000
91101
- stop_any 80 8080
92102

93103
- stage: test
@@ -137,6 +147,16 @@ jobs:
137147
- http :8080/events/00000000-0000-0000-0000-000000000000 Accept:application/json
138148
- http :8080/events
139149
- http delete :8080/events
150+
151+
- http :8080/counter
152+
- http post :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterCreated counterName=hi
153+
- http put :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterIncremented by=travis withValue=1
154+
- http put :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterIncremented withValue=2
155+
- http delete :8080/counter aggregateId=00000000-0000-0000-0000-000000000000 type=CounterSuspended reason="just because I can"
156+
- http :8080/counter/00000000-0000-0000-0000-000000000000
157+
- http :8080/counter
158+
- http delete :8080/events
159+
- http :8080/counter/00000000-0000-0000-0000-000000000000
140160
- stop_any 80 8080
141161

142162
- stage: test

api.http

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ DELETE http://127.0.0.1:8080/events
4545
Accept: application/json
4646

4747
###
48-
POST http://127.0.0.1:8080/events
48+
POST http://127.0.0.1:8080/counter
4949
Content-Type: application/json
5050
Accept: application/json
5151

@@ -56,7 +56,26 @@ Accept: application/json
5656
}
5757

5858
###
59-
GET http://127.0.0.1:8080/events
59+
PUT http://127.0.0.1:8080/counter
60+
Content-Type: application/json
61+
Accept: application/json
62+
63+
{
64+
"aggregateId": "00000000-0000-0000-0000-000000000000",
65+
"by": "me",
66+
"withValue": 3,
67+
"type": "CounterIncremented"
68+
}
69+
70+
###
71+
72+
GET http://127.0.0.1:8080/counter/00000000-0000-0000-0000-000000000000
73+
Content-Type: application/json
74+
Accept: application/json
75+
76+
###
77+
78+
GET http://127.0.0.1:8080/counter
6079
Content-Type: application/json
6180
Accept: application/json
6281

docs/docs/arquillian.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
layout: post
3+
title: "Arquillian guide"
4+
date: 2019-09-02 01:32:00 +0300
5+
---
6+
7+
# TODO: implement me...
8+
9+
1. pom.xml
10+
11+
```xml
12+
<dependencyManagement>
13+
<dependency>
14+
<groupId>org.jboss.arquillian</groupId>
15+
<artifactId>arquillian-bom</artifactId>
16+
<version>1.4.1.Final</version>
17+
<scope>import</scope>
18+
<type>pom</type>
19+
</dependency>
20+
</dependencies>
21+
</dependencyManagement>
22+
```

docs/docs/quick-start.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ layout: post
55
title: "Developer Guide"
66
date: 2019-09-01 05:11:51 +0300
77
---
8+
89
_clone repo_
910

1011
```bash

pom.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<modelVersion>4.0.0</modelVersion>
77
<groupId>com.github.daggerok</groupId>
88
<artifactId>jersey-netty-cdi-jackson-file-eventstore</artifactId>
9-
<version>1.0.0-SNAPSHOT</version>
9+
<version>2.0.0-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<properties>
@@ -34,6 +34,7 @@
3434
<capsule-maven-plugin.version>1.5.1</capsule-maven-plugin.version>
3535
<spotbugs-maven-plugin.version>3.1.12.2</spotbugs-maven-plugin.version>
3636
<maven-surefire-plugin.version>3.0.0-M3</maven-surefire-plugin.version>
37+
<arquillian.version>1.4.1.Final</arquillian.version>
3738
</properties>
3839

3940
<dependencyManagement>
@@ -66,6 +67,13 @@
6667
<scope>import</scope>
6768
<type>pom</type>
6869
</dependency>
70+
<dependency>
71+
<groupId>org.jboss.arquillian</groupId>
72+
<artifactId>arquillian-bom</artifactId>
73+
<version>${arquillian.version}</version>
74+
<scope>import</scope>
75+
<type>pom</type>
76+
</dependency>
6977
</dependencies>
7078
</dependencyManagement>
7179

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package daggerok.domain;
2+
3+
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import com.fasterxml.jackson.annotation.JsonIgnore;
5+
import com.fasterxml.jackson.annotation.JsonInclude;
6+
import daggerok.eventstore.events.CounterCreated;
7+
import daggerok.eventstore.events.CounterIncremented;
8+
import daggerok.eventstore.events.CounterSuspended;
9+
import daggerok.eventstore.events.DomainEvent;
10+
import io.vavr.API;
11+
import io.vavr.collection.List;
12+
import lombok.Getter;
13+
import lombok.ToString;
14+
import lombok.extern.log4j.Log4j2;
15+
16+
import java.time.ZonedDateTime;
17+
import java.util.Collection;
18+
import java.util.Objects;
19+
import java.util.UUID;
20+
import java.util.concurrent.CopyOnWriteArrayList;
21+
import java.util.function.Function;
22+
23+
import static io.vavr.API.$;
24+
import static io.vavr.API.Case;
25+
import static io.vavr.Predicates.instanceOf;
26+
27+
@Getter
28+
@Log4j2
29+
@ToString
30+
public class Counter implements Function<DomainEvent, Counter> {
31+
32+
@JsonIgnore
33+
private final Collection<DomainEvent> eventStream = new CopyOnWriteArrayList<>();
34+
35+
@JsonInclude(JsonInclude.Include.NON_NULL)
36+
private UUID aggregateId;
37+
38+
@JsonInclude(JsonInclude.Include.NON_NULL)
39+
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
40+
private ZonedDateTime createdAt;
41+
42+
@JsonInclude(JsonInclude.Include.NON_NULL)
43+
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
44+
private ZonedDateTime modifiedAt;
45+
46+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
47+
private String name;
48+
49+
@JsonInclude(JsonInclude.Include.NON_NULL)
50+
private Long counter;
51+
52+
@JsonInclude(JsonInclude.Include.NON_NULL)
53+
private boolean suspended;
54+
55+
public Counter() {
56+
this(null, null, null, null, null, true);
57+
}
58+
59+
public Counter(UUID aggregateId,
60+
ZonedDateTime createdAt,
61+
ZonedDateTime modifiedAt,
62+
String name,
63+
Long counter,
64+
boolean suspended) {
65+
66+
this.aggregateId = aggregateId;
67+
this.createdAt = createdAt;
68+
this.modifiedAt = modifiedAt;
69+
this.name = name;
70+
this.counter = counter;
71+
this.suspended = suspended;
72+
}
73+
74+
/* commands */
75+
76+
public void create(UUID id, String counterName) {
77+
if (Objects.nonNull(this.aggregateId))
78+
throw new IllegalStateException("current aggregate already initialized with some aggregateId");
79+
if (Objects.nonNull(this.name))
80+
throw new IllegalStateException("current aggregate already initialized with some name");
81+
if (Objects.isNull(id)) throw new IllegalStateException("id may not be null");
82+
if (Objects.isNull(counterName)) throw new IllegalStateException("counterName may not be null");
83+
on(new CounterCreated(id, counterName, ZonedDateTime.now()));
84+
}
85+
86+
public void increment(String byWhom, Long withValue) {
87+
if (Objects.isNull(this.aggregateId)) throw new IllegalStateException("counter has not been created.");
88+
if (withValue < 1) throw new IllegalStateException("counter can be incremented only with positive numbers.");
89+
on(new CounterIncremented(aggregateId, byWhom, withValue, ZonedDateTime.now()));
90+
}
91+
92+
public void suspend(String byWhom, String reason) {
93+
if (Objects.isNull(this.aggregateId))
94+
throw new IllegalStateException("counter has not been created.");
95+
on(new CounterSuspended(aggregateId, byWhom, reason, ZonedDateTime.now()));
96+
}
97+
98+
/* event sourcing */
99+
100+
public static Counter rebuild(Counter snapshot, Collection<DomainEvent> domainEvents) {
101+
Counter counter = List.ofAll(domainEvents)
102+
.foldLeft(snapshot, Counter::apply);
103+
counter.eventStream.clear();
104+
return counter;
105+
}
106+
107+
@Override
108+
public Counter apply(DomainEvent domainEvent) {
109+
return API.Match(domainEvent).of(
110+
Case($(instanceOf(CounterCreated.class)), this::on),
111+
Case($(instanceOf(CounterIncremented.class)), this::on),
112+
Case($(instanceOf(CounterSuspended.class)), this::on),
113+
Case($(), this::onFallback)
114+
);
115+
}
116+
117+
/* events */
118+
119+
private Counter on(CounterCreated event) {
120+
eventStream.add(event);
121+
aggregateId = event.getAggregateId();
122+
createdAt = modifiedAt = event.getAt();
123+
name = event.getCounterName();
124+
counter = 0L;
125+
suspended = false;
126+
return this;
127+
}
128+
129+
private Counter on(CounterIncremented event) {
130+
eventStream.add(event);
131+
counter += event.getWithValue();
132+
modifiedAt = event.getAt();
133+
log.debug("{} counter incremented by {} with {}", name, event.getBy(), event.getWithValue());
134+
return this;
135+
}
136+
137+
private Counter on(CounterSuspended event) {
138+
eventStream.add(event);
139+
suspended = true;
140+
modifiedAt = event.getAt();
141+
log.debug("{} counter suspended by {} with reason: {}", name, event.getBy(), event.getReason());
142+
return this;
143+
}
144+
145+
private <EVENT extends DomainEvent> Counter onFallback(EVENT event) {
146+
log.warn("unexpected event occurred: {}", event);
147+
return this;
148+
}
149+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package daggerok.domain;
2+
3+
import daggerok.eventstore.EventStore;
4+
import daggerok.eventstore.events.CounterCreated;
5+
import daggerok.eventstore.events.CounterIncremented;
6+
import daggerok.eventstore.events.CounterSuspended;
7+
import daggerok.eventstore.events.DomainEvent;
8+
import lombok.AllArgsConstructor;
9+
import lombok.NoArgsConstructor;
10+
11+
import javax.enterprise.context.RequestScoped;
12+
import javax.inject.Inject;
13+
import javax.json.Json;
14+
import javax.ws.rs.*;
15+
import javax.ws.rs.core.Context;
16+
import javax.ws.rs.core.Response;
17+
import javax.ws.rs.core.UriInfo;
18+
import java.net.URI;
19+
import java.util.Collection;
20+
import java.util.UUID;
21+
22+
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
23+
24+
@RequestScoped
25+
@Path("counter")
26+
@NoArgsConstructor
27+
@Consumes(APPLICATION_JSON)
28+
@Produces(APPLICATION_JSON)
29+
@AllArgsConstructor(onConstructor_ = @Inject)
30+
public class CounterResource {
31+
32+
@Context
33+
private UriInfo uriInfo;
34+
private EventStore eventStore;
35+
36+
@GET
37+
public Response findAll() {
38+
return Response.ok(eventStore.findAll())
39+
.build();
40+
}
41+
42+
@GET
43+
@Path("{aggregateId}")
44+
public Response find(@PathParam("aggregateId") UUID aggregateId) {
45+
Collection<DomainEvent> history = eventStore.read(aggregateId);
46+
return Response.ok(Counter.rebuild(new Counter(), history))
47+
.build();
48+
}
49+
50+
@POST
51+
public Response createCounter(CounterCreated cmd) {
52+
53+
Counter counter = new Counter();
54+
counter.create(cmd.getAggregateId(), cmd.getCounterName());
55+
eventStore.snapshot(counter);
56+
57+
URI url = uriInfo.getBaseUriBuilder()
58+
.path(CounterResource.class)
59+
.path(CounterResource.class, "find")
60+
.build(counter.getAggregateId());
61+
String result = String.format("%s counter created: %s", cmd.getCounterName(), url);
62+
63+
return Response.created(url)
64+
.entity(Json.createObjectBuilder()
65+
.add("result", result)
66+
.build())
67+
.build();
68+
}
69+
70+
@PUT
71+
public Response incrementCounter(CounterIncremented cmd) {
72+
73+
Counter counter = Counter.rebuild(new Counter(), eventStore.read(cmd.getAggregateId()));
74+
counter.increment(cmd.getBy(), cmd.getWithValue());
75+
eventStore.snapshot(counter);
76+
77+
return Response.accepted()
78+
.entity(counter)
79+
.build();
80+
}
81+
82+
@DELETE
83+
public Response incrementCounter(CounterSuspended cmd) {
84+
85+
Counter counter = Counter.rebuild(new Counter(), eventStore.read(cmd.getAggregateId()));
86+
counter.suspend(cmd.getBy(), cmd.getReason());
87+
eventStore.snapshot(counter);
88+
89+
return Response.accepted()
90+
.entity(counter)
91+
.build();
92+
}
93+
}

0 commit comments

Comments
 (0)