Is your feature request related to a problem? Please describe.
@fh-ms @hg-ms @zdenek-jonas : This is not a bug, but a feature suggestion sketch, that I use in my code and I think the ES community can benefit from, if incorporated in ES.
Currently the GigaMap queries, while comprehensive, expose the indexers to clients that use them. Indexers are consider internal implementation detail for a persistent entity, not to be exposed in queries api - think how indexes work in relation/sql databases - they are invisible to queries.
Describe the solution you'd like
I want to share some piece of code that mitigates this issue, by adding a thin layer shielding the calls sites from having to reference internal indexers for queries. This leads to much nicer-to-read code and the benefits compound if you have lot of queries.
This code also applies to mutating operations as well.
Describe alternatives you've considered
Consider the attached code - I propose this to be incorporated in ES at some point.
There are two components - Store and Query builder.
- Store allows for bolier-plate operations - create(if missing) , update, remove. It also creates a Query builders for use by entity clients.
- Query Builder provides fluent, indexer-hinding way to construct much nicer queries.
Examples:
Say you have a an product rating entity:
/// Caches the aggregated rating statistics (average score, total reviews) for a Product.
public final class AggregateRating implements WithTsid {
private final Tsid id;
private final Tsid productId;
private float averageRating;
private int totalReviews;
// ... enum Score {TERRIBLE(1),POOR(2),AVERAGE(3),VERY_GOOD(4),EXCELLENT(5);
private final Map<Rating.Score, Integer> ratingDistribution;
// some code omitted
public static class Store extends AbstractEntityStore<Tsid, AggregateRating> {
//add your indexers - they are private!
private static final BinaryIndexerTsid<AggregateRating> PRODUCT_INDEXER = new BinaryIndexerTsid<>() {
@Override
protected Tsid getTsid(AggregateRating entity) {
return entity.productTsid();
}
};
private static final IndexerFloat<AggregateRating> AVERAGE_RATING_INDEXER = new IndexerFloat.Abstract<>() {
@Override
public Float getFloat(AggregateRating entity) {
return entity.averageRating();
}
};
private static final IndexerInteger<AggregateRating> TOTAL_REVIEWS_INDEXER = new IndexerInteger.Abstract<>() {
@Override
public Integer getInteger(AggregateRating entity) {
return entity.totalReviews();
}
};
public Store() {
super(GigaMap.<AggregateRating>Builder()
.withBitmapIdentityIndex(WithTsid.TSID_INDEXER_BINARY)
.withBitmapIndex(PRODUCT_INDEXER)
.withBitmapIndex(AVERAGE_RATING_INDEXER)
.withBitmapIndex(TOTAL_REVIEWS_INDEXER)
.build());
}
@Override
public Optional<AggregateRating> findById(Tsid id) {
return WithTsid.findOne(gigaMap, id);
}
@Override
public Optional<AggregateRating> addOrGetExisting(AggregateRating entity) {
var existing = findByProduct(entity.productTsid());
if (existing.isPresent()) return existing;
add(entity);
return Optional.empty();
}
@Override
public void add(AggregateRating entity) {
super.add(entity);
RootData.getEclipseStoreContext().addAllToPersist(entity,gigaMap);
}
@Override
public void update(AggregateRating entity, Consumer<AggregateRating> updater) {
super.update(entity, updater);
TourBizData.getEclipseStoreContext().addAllToPersist(entity,gigaMap);
}
@Override
public boolean remove(AggregateRating entity) {
if (super.remove(entity)) {
TourBizData.getEclipseStoreContext().addToPersist(gigaMap);
return true;
}
return false;
}
public Optional<AggregateRating> findByProduct(Tsid productId) {
return gigaMap.query(PRODUCT_INDEXER.is(productId)).findFirst();
}
public void updateMetric(Tsid productTsid, Consumer<AggregateRating> metricUpdater) {
var agg = findByProduct(productTsid).orElseGet(() -> {
var newAgg = new AggregateRating(productTsid);
add(newAgg);
return newAgg;
});
update(agg, metricUpdater);
}
//Create a query builder off this store
public QueryBuilder query() {
return new QueryBuilder();
}
public class QueryBuilder extends GigaQueryBuilder<AggregateRating, QueryBuilder> {
public QueryBuilder() {
super(Store.this.gigaMap);
}
@Override
protected QueryBuilder self() {
return this;
}
public QueryBuilder product(Tsid... productIds) {
return where(PRODUCT_INDEXER, productIds);
}
public QueryBuilder averageRatingGreaterThan(float rating) {
add(AVERAGE_RATING_INDEXER.greaterThan(rating));
return this;
}
public QueryBuilder totalReviewsGreaterThan(int count) {
add(TOTAL_REVIEWS_INDEXER.greaterThan(count));
return this;
}
}
}
}
Then you can query it like that:
var store = dataRoot.aggreagteRationsStore()
var results = store.query()
.product(1,2,3)
.averageRatingGreaterThan(3.0)
.totalReviewsGreaterThan(1000)
.toList();
Additional context
-
GigaQueryBuilder place nicely with GigaMap queries - it can be extended with inheritance or composition of gigamap queries
-
At some point the Store/QueryBuilder can be autogenerated much like the code generators for layered entities
-
If you look at Spring Data and Jakarta EE Data specs - theyr are going in the same directions - convinience by default, that can be overridden and extended.
Note - attached files. EclipseContextStore.java is included in another feature request
Creator.java
AbstractEntityStore.java
GigaQueryBuilder.java
Is your feature request related to a problem? Please describe.
@fh-ms @hg-ms @zdenek-jonas : This is not a bug, but a feature suggestion sketch, that I use in my code and I think the ES community can benefit from, if incorporated in ES.
Currently the GigaMap queries, while comprehensive, expose the indexers to clients that use them. Indexers are consider internal implementation detail for a persistent entity, not to be exposed in queries api - think how indexes work in relation/sql databases - they are invisible to queries.
Describe the solution you'd like
I want to share some piece of code that mitigates this issue, by adding a thin layer shielding the calls sites from having to reference internal indexers for queries. This leads to much nicer-to-read code and the benefits compound if you have lot of queries.
This code also applies to mutating operations as well.
Describe alternatives you've considered
Consider the attached code - I propose this to be incorporated in ES at some point.
There are two components - Store and Query builder.
Examples:
Say you have a an product rating entity:
Then you can query it like that:
Additional context
GigaQueryBuilder place nicely with GigaMap queries - it can be extended with inheritance or composition of gigamap queries
At some point the Store/QueryBuilder can be autogenerated much like the code generators for layered entities
If you look at Spring Data and Jakarta EE Data specs - theyr are going in the same directions - convinience by default, that can be overridden and extended.
Note - attached files.
EclipseContextStore.javais included in another feature requestCreator.java
AbstractEntityStore.java
GigaQueryBuilder.java