Skip to content

Commit

Permalink
Problem: using non-final index definitions is problematic
Browse files Browse the repository at this point in the history
Generally speaking, modifying a static field is a code smell.

The values are not getting cached, and it doesn't work well
with defaults Lombok does for value classes (final statics)

Solution: deprecate existing index definition syntax
and introduce a new one

The new syntax is:

```java
public static final SimpleIndex<MyEvent, UUID> ID = SimpleIndex.as(StandardEntity::uuid);
public static final MultiValueIndex<MyEvent, String> VALS = MultiValueIndex.as(MyEvent::vals);
```

This way, at the expense of a slightly more verbose index,
we're allowing to make index statics final.
  • Loading branch information
yrashk committed Nov 1, 2016
1 parent 6622982 commit 263ef68
Show file tree
Hide file tree
Showing 33 changed files with 234 additions and 143 deletions.
8 changes: 4 additions & 4 deletions docs/getting_started/indexing.md
Expand Up @@ -5,15 +5,15 @@ find restaurant's address changes by its ID. In `AddressChanged`, add this simpl

```java
@NonFinal
public static SimpleIndex<AddressChanged, UUID> REFERENCE_ID = AddressChanged::reference;
public static SimpleIndex<AddressChanged, UUID> REFERENCE_ID = SimpleIndex.as(AddressChanged::reference);
```

If you want to index a comparable, such as event's timestamp (it's a very common scenario!), you need to declare index properties:

```java
@NonFinal
@Index({EQ, LT, GT})
public static SimpleIndex<AddressChanged, HybridTimestamp> TIMESTAMP = StandardEntity::timestamp;
public static SimpleIndex<AddressChanged, HybridTimestamp> TIMESTAMP = SimpleIndex.as(StandardEntity::timestamp);
```

Indexing is not limited to producing just one value. In some cases, like
Expand All @@ -23,10 +23,10 @@ indexing working hours schedule in `WorkingHoursChanged`, an index can produce a
@NonFinal
@Index({EQ, LT, GT})
public static MultiValueIndex<WorkingHoursChanged, OpeningHoursBoundary> OPENING_AT =
(workingHoursChanged) ->
SimpleIndex.as((workingHoursChanged) ->
workingHoursChanged.openDuring().stream()
.map(openingHours ->
new OpeningHoursBoundary(workingHoursChanged.dayOfWeek(),
openingHours.from()))
.collect(Collectors.toList());
.collect(Collectors.toList()));
```
Expand Up @@ -38,12 +38,12 @@ public class Deleted extends StandardEvent {
final UUID reference;

@Index
public static SimpleIndex<Deleted, UUID> ID = StandardEntity::uuid;
public final static SimpleIndex<Deleted, UUID> ID = SimpleIndex.as(StandardEntity::uuid);

public static SimpleIndex<Deleted, UUID> REFERENCE_ID = Deleted::reference;
public final static SimpleIndex<Deleted, UUID> REFERENCE_ID = SimpleIndex.as(Deleted::reference);

@Index({EQ, LT, GT})
public static SimpleIndex<Deleted, HybridTimestamp> TIMESTAMP = StandardEntity::timestamp;
public final static SimpleIndex<Deleted, HybridTimestamp> TIMESTAMP = SimpleIndex.as(StandardEntity::timestamp);

@LayoutConstructor
public Deleted(UUID reference) {
Expand Down
Expand Up @@ -49,10 +49,10 @@ public DescriptionChanged(UUID reference, String description, HybridTimestamp ti
this.description = description;
}

public static SimpleIndex<DescriptionChanged, UUID> REFERENCE_ID = DescriptionChanged::reference;
public final static SimpleIndex<DescriptionChanged, UUID> REFERENCE_ID = SimpleIndex.as(DescriptionChanged::reference);

public static SimpleIndex<DescriptionChanged, String> DESCRIPTION = DescriptionChanged::description;
public final static SimpleIndex<DescriptionChanged, String> DESCRIPTION = SimpleIndex.as(DescriptionChanged::description);

@Index({LT, GT, EQ})
public static SimpleIndex<DescriptionChanged, HybridTimestamp> TIMESTAMP = StandardEntity::timestamp;
public final static SimpleIndex<DescriptionChanged, HybridTimestamp> TIMESTAMP = SimpleIndex.as(StandardEntity::timestamp);
}
Expand Up @@ -49,10 +49,10 @@ public NameChanged(UUID reference, String name, HybridTimestamp timestamp) {
this.name = name;
}

public static SimpleIndex<NameChanged, UUID> REFERENCE_ID = NameChanged::reference;
public final static SimpleIndex<NameChanged, UUID> REFERENCE_ID = SimpleIndex.as(NameChanged::reference);

public static SimpleIndex<NameChanged, String> NAME = NameChanged::name;
public final static SimpleIndex<NameChanged, String> NAME = SimpleIndex.as(NameChanged::name);

@Index({LT, GT, EQ})
public static SimpleIndex<NameChanged, HybridTimestamp> TIMESTAMP = StandardEntity::timestamp;
public final static SimpleIndex<NameChanged, HybridTimestamp> TIMESTAMP = SimpleIndex.as(StandardEntity::timestamp);
}
Expand Up @@ -36,10 +36,10 @@ public class Undeleted extends StandardEvent {
@Getter
final UUID deleted;

public static SimpleIndex<Undeleted, UUID> DELETED_ID = Undeleted::deleted;
public final static SimpleIndex<Undeleted, UUID> DELETED_ID = SimpleIndex.as(Undeleted::deleted);

@Index({EQ, LT, GT})
public static SimpleIndex<Undeleted, HybridTimestamp> TIMESTAMP = StandardEntity::timestamp;
public final static SimpleIndex<Undeleted, HybridTimestamp> TIMESTAMP = SimpleIndex.as(StandardEntity::timestamp);

@LayoutConstructor
public Undeleted(UUID deleted) {
Expand Down
Expand Up @@ -43,7 +43,7 @@ public static class Create extends StandardCommand<Created, Created> {
}
}
public static class Created extends StandardEvent {
public static SimpleIndex<Created, UUID> ID = StandardEntity::uuid;
public final static SimpleIndex<Created, UUID> ID = SimpleIndex.as(StandardEntity::uuid);
}

@Accessors(fluent = true)
Expand Down
Expand Up @@ -19,5 +19,5 @@
@LayoutName("rfc.eventsourcing.com/spec:9/RIG/#CommandTerminatedExceptionally")
public class CommandTerminatedExceptionally extends StandardEvent {

public static SimpleIndex<CommandTerminatedExceptionally, UUID> ID = StandardEntity::uuid;
public final static SimpleIndex<CommandTerminatedExceptionally, UUID> ID = SimpleIndex.as(StandardEntity::uuid);
}
Expand Up @@ -35,7 +35,7 @@ public EventCausalityEstablished(HybridTimestamp timestamp, UUID event, UUID com
this.command = command;
}

public static SimpleIndex<EventCausalityEstablished, UUID> EVENT = EventCausalityEstablished::event;
public final static SimpleIndex<EventCausalityEstablished, UUID> EVENT = SimpleIndex.as(EventCausalityEstablished::event);

public static SimpleIndex<EventCausalityEstablished, UUID> COMMAND = EventCausalityEstablished::command;
public final static SimpleIndex<EventCausalityEstablished, UUID> COMMAND = SimpleIndex.as(EventCausalityEstablished::command);
}
Expand Up @@ -70,6 +70,6 @@ public JavaExceptionOccurred(Exception t) {
map(StackTraceElement::new).collect(Collectors.toList());
}

public static SimpleIndex<JavaExceptionOccurred, UUID> ID = StandardEntity::uuid;
public final static SimpleIndex<JavaExceptionOccurred, UUID> ID = SimpleIndex.as(StandardEntity::uuid);

}
Expand Up @@ -27,7 +27,6 @@
*/
public interface EntityQueryFactory {


/**
* Creates an {@link Equal} query which asserts that an attribute equals a certain value.
*
Expand Down Expand Up @@ -1088,4 +1087,5 @@ public String toString() {
static <O extends Entity> Query<EntityHandle<O>> none(Class<O> objectType) {
return new None<>(objectType);
}

}
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.eventsourcing.index;

import com.eventsourcing.Entity;

public interface IndexWithAttribute<O extends Entity, A> {
Attribute<O, A> getAttribute();
void setAttribute(Attribute<O, A> attribute);
}
Expand Up @@ -19,13 +19,16 @@
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;

import com.googlecode.cqengine.index.Index;
import lombok.extern.slf4j.Slf4j;
import org.osgi.service.component.annotations.Component;

import static com.eventsourcing.index.IndexEngine.IndexFeature.EQ;

@Component
@Slf4j
public class JavaStaticFieldIndexLoader implements IndexLoader {

@SneakyThrows
Expand All @@ -39,9 +42,6 @@ public class JavaStaticFieldIndexLoader implements IndexLoader {
for (Field field : klass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) &&
EntityIndex.class.isAssignableFrom(field.getType())) {
if (Modifier.isFinal(field.getModifiers())) {
throw new IllegalArgumentException("Index attribute " + field + " can't be declared final");
}

com.eventsourcing.index.Index annotation = field
.getAnnotation(com.eventsourcing.index.Index.class);
Expand All @@ -56,34 +56,25 @@ public class JavaStaticFieldIndexLoader implements IndexLoader {

if (SimpleIndex.class.isAssignableFrom(field.getType())) {
attribute = new EntitySimpleAttribute(objectType, entityType, attributeType, field, index);
field.set(null, new SimpleIndex() {
@Override public Attribute getAttribute() {
return attribute;
}

@Override public Object getValue(Entity object) {
return ((SimpleIndex) index).getValue(object);
}

@Override public Object getValue(Entity object, QueryOptions queryOptions) {
return ((SimpleIndex) index).getValue(object, queryOptions);
}
});
if (index instanceof IndexWithAttribute) {
((IndexWithAttribute) index).setAttribute(attribute);
} else {
log.warn("Non-final index definitions are deprecated, use SimpleIndex.as() instead");
WrappedSimpleIndex wrappedIndex = new WrappedSimpleIndex<>((BiFunction<Entity, QueryOptions, Object>) ((SimpleIndex) index)::getValue);
wrappedIndex.setAttribute(attribute);
field.set(null, wrappedIndex);
}
} else {
attribute = new MultiValueEntityAttribute(objectType, entityType, attributeType, field, index);
field.set(null, new MultiValueIndex() {
@Override public Attribute getAttribute() {
return attribute;
}

@Override public Iterable getValues(Entity object) {
return ((MultiValueIndex)index).getValues(object);
}

@Override public Iterable getValues(Entity object, QueryOptions queryOptions) {
return index.getValues(object, queryOptions);
}
});
if (index instanceof IndexWithAttribute) {
((IndexWithAttribute) index).setAttribute(attribute);
} else {
log.warn("Non-final index definitions are deprecated, use MultiValueIndex.as() instead");
WrappedMultiValueIndex wrappedIndex = new WrappedMultiValueIndex(((BiFunction<Entity,
QueryOptions, Object>) ((MultiValueIndex) index)::getValues));
wrappedIndex.setAttribute(attribute);
field.set(null, wrappedIndex);
}
}
indices.add(engine.getIndexOnAttribute(attribute, features));
}
Expand Down
Expand Up @@ -11,6 +11,7 @@
import com.googlecode.cqengine.query.option.QueryOptions;

import java.util.function.BiFunction;
import java.util.function.Function;

/**
* Designates a multiple values index using a functional interface
Expand Down Expand Up @@ -44,6 +45,19 @@ default Iterable<A> getValues(O object, QueryOptions queryOptions) {

Iterable<A> getValues(O object);


/**
* Creates a MultiValueIndex with a function that can access {@link QueryOptions}
* @param function
* @param <O>
* @param <A>
* @return
*/
static <O extends Entity, A> MultiValueIndex<O, A> as(Function<O, Iterable<A>> function) {
return new WrappedMultiValueIndex<>(function);
}


/**
* Creates a MultiValueIndex with a function that can access {@link QueryOptions}
* @param function
Expand All @@ -53,17 +67,10 @@ default Iterable<A> getValues(O object, QueryOptions queryOptions) {
*/
static <O extends Entity, A> MultiValueIndex<O, A>
withQueryOptions(BiFunction<O, QueryOptions, Iterable<A>> function) {
return new MultiValueIndex<O, A>() {
@Override public Iterable<A> getValues(O object, QueryOptions queryOptions) {
return function.apply(object, queryOptions);
}

@Override public Iterable<A> getValues(O object) {
return function.apply(object, EntityQueryFactory.noQueryOptions());
}
};
return new WrappedMultiValueIndex<>(function);
}


default Attribute<O, A> getAttribute() {
throw new IllegalStateException("Index " + this + " hasn't been initialized yet");
}
Expand Down
Expand Up @@ -12,6 +12,7 @@

import java.util.Collections;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
* Designates a simple (single value) index using a functional interface.
Expand Down Expand Up @@ -44,6 +45,18 @@ default A getValue(O object, QueryOptions queryOptions) {
}
A getValue(O object);

/**
* Creates a SimpleIndex
*
* @param function
* @param <O>
* @param <A>
* @return
*/
static <O extends Entity, A> SimpleIndex<O, A> as(Function<O, A> function) {
return new WrappedSimpleIndex<>(function);
}

/**
* Creates a SimpleIndex with a function that can access {@link QueryOptions}
*
Expand All @@ -53,15 +66,7 @@ default A getValue(O object, QueryOptions queryOptions) {
* @return
*/
static <O extends Entity, A> SimpleIndex<O, A> withQueryOptions(BiFunction<O, QueryOptions, A> function) {
return new SimpleIndex<O, A>() {
@Override public A getValue(O object, QueryOptions queryOptions) {
return function.apply(object, queryOptions);
}

@Override public A getValue(O object) {
return function.apply(object, EntityQueryFactory.noQueryOptions());
}
};
return new WrappedSimpleIndex<>(function);
}

default Iterable<A> getValues(O object, QueryOptions queryOptions) {
Expand Down
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.eventsourcing.index;

import com.eventsourcing.Entity;
import com.googlecode.cqengine.query.option.QueryOptions;
import lombok.Getter;
import lombok.Setter;

import java.util.function.BiFunction;
import java.util.function.Function;

import static com.eventsourcing.index.EntityQueryFactory.noQueryOptions;

class WrappedMultiValueIndex<O extends Entity, A, I extends Iterable<A>> implements MultiValueIndex<O, A>,
IndexWithAttribute<O, A> {
private final BiFunction<O, QueryOptions, I> index;
@Getter @Setter
private Attribute<O, A> attribute;

public WrappedMultiValueIndex(Function<O, I> index) {this.index = (object, queryOptions) -> index.apply(object);}
public WrappedMultiValueIndex(BiFunction<O, QueryOptions, I> index) {this.index = index;}


@Override public Iterable<A> getValues(O object) {
return index.apply(object, noQueryOptions());
}

@Override public Iterable<A> getValues(O object, QueryOptions queryOptions) {
return index.apply(object, queryOptions);
}
}
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.eventsourcing.index;

import com.eventsourcing.Entity;
import com.googlecode.cqengine.attribute.*;
import com.googlecode.cqengine.query.option.QueryOptions;
import lombok.Getter;
import lombok.Setter;

import java.util.function.BiFunction;
import java.util.function.Function;

import static com.eventsourcing.index.EntityQueryFactory.noQueryOptions;

class WrappedSimpleIndex<O extends Entity, A> implements SimpleIndex<O, A>, IndexWithAttribute<O, A> {
private final BiFunction<O, QueryOptions, A> index;
@Getter @Setter
private Attribute<O, A> attribute;

public WrappedSimpleIndex(Function<O, A> index) {this.index = (e, queryOptions) -> index.apply(e);}
public WrappedSimpleIndex(BiFunction<O, QueryOptions, A> index) {this.index = index;}

@Override public A getValue(O object) {
return index.apply(object, noQueryOptions());
}

@Override public A getValue(O object, QueryOptions queryOptions) {
return index.apply(object, queryOptions);
}
}

0 comments on commit 263ef68

Please sign in to comment.