devnation'15 - using lambda expressions to query a datastore

48
Using Lambda Expressions to Query a Datastore Xavier Coulon - Red Hat @xcoulon

Upload: xavier-coulon

Post on 12-Apr-2017

327 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: DevNation'15 - Using Lambda Expressions to Query a Datastore

Using Lambda Expressions to Query a

Datastore

Xavier Coulon - Red Hat @xcoulon

Page 2: DevNation'15 - Using Lambda Expressions to Query a Datastore

The idea

Page 3: DevNation'15 - Using Lambda Expressions to Query a Datastore

The idea

Lambda Expressions:

• provide type safety

• describe the "what", not the "how"

Page 4: DevNation'15 - Using Lambda Expressions to Query a Datastore

The idea

Page 5: DevNation'15 - Using Lambda Expressions to Query a Datastore

The idea

u -> ( u.firstName.equals("john") && u.lastName.equals("doe")) || u.userName.equals("jdoe"))

final List<User> users = userCollection.filter( u -> ( u.firstName.equals("john") && u.lastName.equals("doe")) || u.userName.equals("jdoe")).toList();

Page 6: DevNation'15 - Using Lambda Expressions to Query a Datastore

Introducing project

Lambdamatic

Page 7: DevNation'15 - Using Lambda Expressions to Query a Datastore

Part I. The building blocks

Page 8: DevNation'15 - Using Lambda Expressions to Query a Datastore

Building blocks

• Metamodel generation • Lambda Expression analysis

Page 9: DevNation'15 - Using Lambda Expressions to Query a Datastore

Metamodel generation (à la JPA)

Page 10: DevNation'15 - Using Lambda Expressions to Query a Datastore

package com.example.domain;

public class User { private String id; private String firstName; private String lastName; private String userName;

}

Metamodel generation

package com.example.domain;

@Document(collection="users") public class User {

@DocumentId private String id;

@DocumentField private String firstName;

private String lastName;

@DocumentField(name="uName") private String userName; }

package com.example.domain;

@Generated(value="LambdamaticAnnotationsProcessor") public class QUser implements QueryMetadata<User> {

@DocumentField(name="_id") public StringField firstName;

@DocumentField(name="firstName") public StringField firstName;

@DocumentField(name="lastName") public StringField lastName;

@DocumentField(name="uName") public StringField userName; }

Query Metadata class generation

Page 11: DevNation'15 - Using Lambda Expressions to Query a Datastore

Metamodel generation

package com.example.domain;

@Generated(value="o.l.m.a.DocumentAnnotationProcessor") public class UserCollection extends LambdamaticMongoCollectionImpl<User, QUser> {

public UserCollection(final MongoClient mongoClient, final String databaseName) { super(mongoClient, databaseName, "users", User.class); }

}

Collection classpackage com.example.domain;

@Document(collection="users") public class User {

@DocumentId private String id;

@DocumentField private String firstName;

private String lastName;

@DocumentField(name="uName") private String userName; }

Page 12: DevNation'15 - Using Lambda Expressions to Query a Datastore

Metamodel generation

CDI Integration

@Generated(value="org.lambdamatic.mongodb.apt.DocumentAnnotationProcessor") @ApplicationScoped public class UserCollectionProducer { @Produces public UserCollection getUserCollection(final MongoClient mongoClient, final MongoClientConfiguration mongoClientConfiguration) { return new UserCollection(mongoClient, mongoClientConfiguration.getDatabaseName(), "users"); }

}

Page 13: DevNation'15 - Using Lambda Expressions to Query a Datastore

final List<User> users = userCollection.filter( u -> ( u.firstName.equals("john") && u.lastName.equals("doe")) || u.userName.equals("jdoe")).toList();

Metamodel in action

public class UserCollection extends LambdamaticMongoCollectionImpl<User, QUser> { ... }

package com.example.domain;

@Generated(value="LambdamaticAnnotationsProcessor") public class QUser implements QueryMetadata<User> {

@DocumentField(name="_id") public StringField firstName;

@DocumentField(name="firstName") public StringField firstName;

... }

Page 14: DevNation'15 - Using Lambda Expressions to Query a Datastore

Metamodel benefits

Geospatial queries

Page 15: DevNation'15 - Using Lambda Expressions to Query a Datastore

Metamodel benefits

public class Location {

/** The latitude value.*/ private double latitude;

/** The longitude value.*/ private double longitude;

}

public List<BikeStation> findWithin(final Location[] corners) { return bikeStationCollection.find(s -> s.location.geoWithin(corners)) .toList(); }

public class QBikeStation implements QueryMetadata<BikeStation> {

@DocumentField(name="location") public LocationField location;

...

}

@Document(collection="bikestations") public class BikeStation { @DocumentField private Location location;

...

}

public interface LocationField {

public boolean geoWithin(final Location[] corners);

... }

Page 16: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

(this is the fun part, really)

Page 17: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

Page 18: DevNation'15 - Using Lambda Expressions to Query a Datastore
Page 19: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

@FunctionalInterfacepublic interface FilterExpression<T> extends Predicate<T>, Serializable { }

Step#1 : Use a Serializable Functional Interface

Page 20: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

public static <T> SerializedLambda getSerializedLambda( final FilterExpression<T> expression) { ... final Class<?> cl = expression.getClass(); final Method m = cl.getDeclaredMethod("writeReplace"); m.setAccessible(true); return (SerializedLambda) m.invoke(expression); ... }

Step#2 : Locate the generated method

Page 21: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

Step#3 : read the generated bytecode Label L1125736023 VarInsn ALOAD #0 FieldInsn GETFIELD com/sample/QUser.firstName (desc=Lorg/lambdamatic/mongodb/metadata/StringField;) LdcInsn john MethodInsn INVOKEVIRTUAL java/lang/Object.equals (desc=(Ljava/lang/Object;)Z) JumpInsn IFEQ L1636182655 VarInsn ALOAD #0 FieldInsn GETFIELD com/sample/QUser.lastName (desc=Lorg/lambdamatic/mongodb/metadata/StringField;) Label L1122606666 LdcInsn doe MethodInsn INVOKEVIRTUAL java/lang/Object.equals (desc=(Ljava/lang/Object;)Z) JumpInsn IFNE L350068407 Label L1636182655 VarInsn ALOAD #0 FieldInsn GETFIELD com/sample/QUser.userName (desc=Lorg/lambdamatic/mongodb/metadata/StringField;) LdcInsn jdoe MethodInsn INVOKEVIRTUAL java/lang/Object.equals (desc=(Ljava/lang/Object;)Z) JumpInsn IFNE L350068407 Insn ICONST_0 JumpInsn GOTO L1820383114 Label L350068407 Insn ICONST_1 Label L1820383114 Insn IRETURN Label L369049246 LocalVariable u (desc=Lcom/sample/QUser;) index=0

Page 22: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

Step#4 : build an AST

if(u.firstName.equals("john")) { if(u.lastName.equals("doe")) { return true } else { if(u.userName.equals("jdoe")) { return true } else { return false } } } else { if(u.userName.equals("jdoe")) { return true } else { return false } }

Page 23: DevNation'15 - Using Lambda Expressions to Query a Datastore

if(u.firstName.equals("john")) { if(u.lastName.equals("doe")) { return true } else { if(u.userName.equals("jdoe")) { return true } else { return false } } } else { if(u.userName.equals("jdoe")) { return true } else { return false } }

if(u.firstName.equals("john")) { if(u.lastName.equals("doe")) { return true } else { if(u.userName.equals("jdoe")) { return true } else { return false } } } else { if(u.userName.equals("jdoe")) { return true } else { return false }

(u.firstName.equals("john") && u.lastName.equals("doe"))

|| (u.firstName.equals("john") && !u.lastName.equals("doe") && u.userName.equals("jdoe"))

|| (!u.firstName.equals("john") && u.userName.equals("jdoe"))

Lambda Expression Analysis

Step#5 : Thin out the AST

Page 24: DevNation'15 - Using Lambda Expressions to Query a Datastore

(u.firstName.equals("john") && u.lastName.equals("doe"))

|| (u.firstName.equals("john") && !u.lastName.equals("doe") && u.userName.equals("jdoe"))

|| (!u.firstName.equals("john") && u.userName.equals("jdoe"))

Lambda Expression Analysis

Step#6 : Further simplify the AST

(u.firstName.equals("john") && u.lastName.equals("doe"))

|| (u.firstName.equals("john") && !u.lastName.equals("doe") && u.userName.equals("jdoe"))

|| (!u.firstName.equals("john") && u.userName.equals("jdoe"))

Page 25: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

Boolean algebra to the rescue !a.(b.c) = a.b.c a + (b + c) = a + b + c a.(!a + b).(!a + c) = a.b.c a + (!a.b) + (!a.c) = a + b + c a.(a + b).(a + c) = a a + (a.b) + (a.c) = a (a.b) + (a.c) + (a.d) = a.(b + c + d) (a + b).(a + c).(a + d) = a + (b.c.d) a + a = a a.a = a a.!a = O a + !a = I O + a = a O.a = O 1 + a = 1 1.a = a a.(b + c + d) = (a.b) + (a.c) + (a.d) a + (b.c.d) = (a + b).(a + c).(a + d)

Page 26: DevNation'15 - Using Lambda Expressions to Query a Datastore
Page 27: DevNation'15 - Using Lambda Expressions to Query a Datastore

Lambda Expression Analysis

Step#6 : further simplify the statement tree

(u.firstName.equals("john") && u.lastName.equals("doe"))

|| (u.firstName.equals("john") && !u.lastName.equals("doe") && u.userName.equals("jdoe"))

|| (!u.firstName.equals("john") && u.userName.equals("jdoe"))

(u.firstName.equals("john") && u.lastName.equals("doe")) || u.userName.equals("jdoe")

Page 28: DevNation'15 - Using Lambda Expressions to Query a Datastore
Page 29: DevNation'15 - Using Lambda Expressions to Query a Datastore

Performances ?

• Byte code is analyzed on first access to Lambda Expression ( <100ms)

• "Raw" AST is cached for subsequent calls

• Placeholders in AST for captured arguments

Page 30: DevNation'15 - Using Lambda Expressions to Query a Datastore

Part II. Managing data on MongoDB

Page 31: DevNation'15 - Using Lambda Expressions to Query a Datastore

Managing Data on MongoDB

• Providing Codecs for Lambda Expressions and Documents

• Skipping the "DBObject" DTOs layer

MongoDB Java Driver Integration

Page 32: DevNation'15 - Using Lambda Expressions to Query a Datastore

Querying MongoDB

Service Layer

Lambda Expression

MongoDB Java Driver

BSON Document

MongoDB

FilterExpression Codec

BSON Document

Domain object(s)

Document Codec

Page 33: DevNation'15 - Using Lambda Expressions to Query a Datastore

Demo #1 Geolocation queries

Page 34: DevNation'15 - Using Lambda Expressions to Query a Datastore

Three more things (yes, three)

Page 35: DevNation'15 - Using Lambda Expressions to Query a Datastore

Query on Arrays

Page 36: DevNation'15 - Using Lambda Expressions to Query a Datastore

Query on Arrays

Page 37: DevNation'15 - Using Lambda Expressions to Query a Datastore

Query on Arrays

final List<BlogEntry> blogEntries = blogEntryCollection.filter( e -> e.comments.elementMatch(

c -> c.author.equals("anonymous") && c.date.greaterOrEquals(twoDaysAgo))) .toList();

final List<BlogEntry> blogEntries = blogEntryCollection.filter( e -> e.comments.author.equals("anonymous") && e.comments.date.greaterOrEquals(twoDaysAgo)) .toList();

Page 38: DevNation'15 - Using Lambda Expressions to Query a Datastore

Query Projections

Page 39: DevNation'15 - Using Lambda Expressions to Query a Datastore

Query Projection

Projection Metadata class generation

@Generated(value="o.l.m.a.DocumentAnnotationProcessor") public abstract class PBlogEntry implements ProjectionMetadata<BlogEntry> {

@DocumentField(name="_id") public ProjectionField id; @DocumentField(name="authorName") public ProjectionField authorName; @DocumentField(name="publishDate") public ProjectionField publishDate;

... }

@Document(collection="blog") public class BlogEntry {

@DocumentId private String id;

@DocumentField private String authorName;

private Date publishDate;

... }

Page 40: DevNation'15 - Using Lambda Expressions to Query a Datastore

Query Projection

import static org.lambdamatic.mongodb.Projection.include;

final BlogEntry blogEntry = blogEntries.filter(e -> e.id.equals("1")).projection(e -> include(e.authorName, e.title, e.publishDate))

.first();

@Generated(value="o.l.m.a.DocumentAnnotationProcessor") public abstract class PBlogEntry implements ProjectionMetadata<BlogEntry> {

@DocumentField(name="_id") public ProjectionField id; @DocumentField(name="authorName") public ProjectionField authorName; @DocumentField(name="publishDate") public ProjectionField publishDate; ... }

public interface Projection { @IncludeFields public static void include(final ProjectionField... fields) {}

@ExcludeFields public static void exclude(final ProjectionField... fields) {} }

Page 41: DevNation'15 - Using Lambda Expressions to Query a Datastore

Query Projection

Projecting array elements

final BlogEntry blogEntry = blogEntryCollection.filter(e -> e.id.equals("1")) .projection(e -> include(e.authorName, e.title, e.publishDate, e.comments.elementMatch(c -> c.author.equals("anonymous") && c.date.greaterOrEquals(twoDaysAgo)))) .first();

Page 42: DevNation'15 - Using Lambda Expressions to Query a Datastore

Update Operations

Page 43: DevNation'15 - Using Lambda Expressions to Query a Datastore

Update Operations

@Document(collection="blog") public class BlogEntry {

@DocumentId private String id; /** Name of author of the blog entry. */ private String authorName; /** Title of the Blog Entry */ private String title; /** list of comments. */ private List<BlogEntryComment> comments; ... }

Update Metadata class generation@Generated(value="o.l.m.a.DocumentAnnotationProcessor") public abstract class UBlogEntry implements UpdateMetadata<BlogEntry> {

@DocumentField(name="_id") public String id; @DocumentField(name="authorName") public String authorName; @DocumentField(name="title") public String title; @DocumentField(name="comments") public UBlogEntryCommentArray comments; ... }

Page 44: DevNation'15 - Using Lambda Expressions to Query a Datastore

Update Operations

BlogEntryComment comment = ... ; blogEntries.filter(e -> e.id.equals("1")). forEach(e -> { e.lastUpdate = new Date(); e.commentsNumber++; e.comments.push(comment);});

@Document(collection="blog") public class BlogEntry {

@DocumentId private String id; /** date of last update. */ private Date lastUpdate; /** Number of comments */ private int commentsNumber; /** list of comments. */ private List<BlogEntryComment> comments;

@EmbeddedDocument public class BlogEntryComment {

/** comment author. */ private String author; /** comment date. */ private Date date; /** comment content. */ private String content;

Page 45: DevNation'15 - Using Lambda Expressions to Query a Datastore

Demo #2 Filter with projections

and Update

Page 46: DevNation'15 - Using Lambda Expressions to Query a Datastore

Project Status

Experimental

Page 47: DevNation'15 - Using Lambda Expressions to Query a Datastore

Project Info

http://github.com/lambdamatic

• Give it a try !

• Clone it / Fork it / Star it

• Open issues to discuss about API and features

Page 48: DevNation'15 - Using Lambda Expressions to Query a Datastore

Thanks ! Q/A