google cloud endpoints - javacro conference · motivation • dead simple web backends for multi-...
TRANSCRIPT
Google Cloud EndpointsEndpoints
Tomislav Čoh, Calyx d.o.o.
Summary
• Introduction
• Motivation
• Architecture• Architecture
• Demo app "Re-gifter"
• Conclusion
Introduction
• Experimental App Engine feature
• REST & RPC APIs for app backend
• Utilizes internal Google infrastructure• Utilizes internal Google infrastructure
• API explorer
• Client library generation:o Java / Android
o Objective C / iOS
o Javascript
Motivation
• Dead simple Web backends for multi-platform clients
• Utilize the power of App EngineDatastoreo Datastore
o Blobstore
o Image service
o Google OAuth2 integration
• Rich testing environment
• Enforced scalability
• Pay for what you use
Architecture
Demo appDemo appre-gifter.appspot.com
Development process• Project setup• Design data model• Develop business logic• Test business logic• Test business logic• Annotate endpoints• Test on local server• Deploy• Test deployed• Generate client libraries• Add authentication
Project setup
• Install Google Plugin for Eclipse
• Create Web Application project
• Disable DataNucleus Enhancer•o Google->App Engine->Orm
• Install Lombok
• Add Objectify to classpath
• Add Guava to classpath
• Add Lombok to classpath
• Setup Objectify
web.xml:
...<filter>
<filter-name>ObjectifyFilter</filter-name><filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter><filter-mapping><filter-name>ObjectifyFilter</filter-name>
public class OfyService {
static {factory().register(Gift.class);
}
public static final Objectify ofy() {return ObjectifyService.ofy();
}
Project setup
<filter-name>ObjectifyFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>...
}
public static final ObjectifyFactory factory() {return ObjectifyService.factory();
}
}
Design data model
• Schemaless datastore• Ancestor paths and entity groups• Datastore Java API•
o Datastore low-level APIo JDO/JPAo Objectifyo Twigo Slim3
@Entity@NoArgsConstructor@RequiredArgsConstructorpublic class Gift {
@Id@Getterprivate Long id;
@Parent@Setterprivate Key recipient;
Design data model
@Getter@NonNullprivate String giver;
@Getter@NonNullprivate String description;
}
Develop business logic
• Find gifts by recipient
• Insert a gift
public class Regifter {
public List<Gift> findGiftsByRecipient(String recipientEmail) {Key recipientKey = KeyFactory.createKey("Person", recipientEmail);return ofy().load().type(Gift.class).ancestor(recipientKey).list();
}
public Gift insertGift(String recipientEmail, Gift gift) {Key recipientKey = KeyFactory.createKey("Person", recipientEmail);gift.setRecipient(recipientKey);
Develop business logic
gift.setRecipient(recipientKey);ofy().save().entity(gift).now();return gift;
}
}
Test
• Create test project• Add Re-gifter project to classpath• Project dependencies:o JUnit4
•o JUnit4o ${SDK_ROOT}/lib/impl/appengine-api.jaro ${SDK_ROOT}/lib/impl/appengine-api-labs.jaro ${SDK_ROOT}/lib/impl/appengine-api-stubs.jaro ${SDK_ROOT}/lib/testing/appengine-testing.jar
• Test business logic
public class RegifterTest {private final LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
private Regifter mRegifter;
@Beforepublic void setUp() {
helper.setUp();mRegifter = new Regifter();
}@Afterpublic void tearDown() {
helper.tearDown();}@Testpublic void allInOneTest() {
Gift gift = new Gift("[email protected]", "Cash");
Test
Gift gift = new Gift("[email protected]", "Cash");
Long giftId = mRegifter.insertGift("[email protected]", gift).getId();
List<Gift> gifts = mRegifter.findGiftsByRecipient("[email protected]");assertEquals(gifts.size(), 1);Gift loadedGift = gifts.get(0);assertEquals(gift.getDescription(), loadedGift.getDescription());assertNotNull(loadedGift.getId());assertEquals(giftId, loadedGift.getId());
}}
Annotate endpoints
• Expose REST & RPC API
• @Api - endpoint configuration
• @ApiMethod - method configuration• @ApiMethod - method configuration
@Api(name = "regifter", version = "v1")public class Regifter {
@ApiMethod(name = "gifts.list", path = "receiver/{receiverEmail}/gift", httpMethod = HttpMethod.GET)public List<Gift> findGiftsByRecipient(@Named("receiverEmail") String recipientEmail) {
Key recipientKey = KeyFactory.createKey("Person", recipientEmail);return ofy().load().type(Gift.class).ancestor(recipientKey).list();
}
@ApiMethod(name = "gifts.insert", path = "receiver/{receiverEmail}/gift", httpMethod = HttpMethod.POST)public Gift insertGift(@Named("receiverEmail") String recipientEmail, Gift gift) {
Key recipientKey = KeyFactory.createKey("Person", recipientEmail);gift.setRecipient(recipientKey);
Annotate endpoints
gift.setRecipient(recipientKey);ofy().save().entity(gift).now();return gift;
}
}
Test on local server
• Run project in Eclipse
• http://localhost:8888/_ah/api/regifter/v1/
• Mocked App Engine environment• Mocked App Engine environmento jetty server instance
o in-memory datastore
o mocked app engine services
$ curl --header "Content-Type: application/json" -X POST -d '{"giver": "[email protected]", "description":
"Fusilli Jerry"}' http://localhost:8888/_ah/api/regifter/v1/receiver/[email protected]/gift
{
"id" : "1",
"giver" : "[email protected]",
"description" : "Fusilli Jerry"
}
$ curl --header "Content-Type: application/json" -X POST -d '{"giver": "[email protected]", "description":
"Cash"}' http://localhost:8888/_ah/api/regifter/v1/receiver/[email protected]/gift
{
"id" : "1",
"giver" : "[email protected]",
"description" : "Cash"
}
$ curl --header "Content-Type: application/json"
http://localhost:8888/_ah/api/regifter/v1/receiver/[email protected]/gift
{
"items" : [ {
"id" : "1",
Test on local server
"id" : "1",
"giver" : "[email protected]",
"description" : "Fusilli Jerry"
} ]
}
$ curl --header "Content-Type: application/json"
http://localhost:8888/_ah/api/regifter/v1/receiver/[email protected]/gift
{
"items" : [ {
"id" : "1",
"giver" : "[email protected]",
"description" : "Cash"
} ]
}
Deploy
• https://appengine.google.com
• Set application id
• Authorize Google Plugin for Eclipse• Authorize Google Plugin for Eclipse
• Right Click->Google->Deploy to App Engine
• ...
• Profit
Test deployed
• API Explorer
• https://re-gifter.appspot.com/_ah/api/explorer
• API discovery• API discovery
• Test OAuth2 authorized APIs
Test deployed
Generate client libraries• Right Click->Google->Generate Cloud Endpoint Client Library
• App Engine Connected Android Project
o Google Cloud Endpoints
o Google Cloud Messagingo Google Cloud Messaging
Android:Regifter.Builder builder = new Regifter.Builder(AndroidHttp.newCompatibleTransport(), new
GsonFactory(), null);
Regifter mRegifter = builder.build();
Gift fusilliJerry = new Gift();
fusilliJerry.setGiver("[email protected]");
fusilliJerry.setDescription("Fusilli Jerry");
fusillyJerry = mRegifter.gifts().insert("[email protected]", fusilliJerry).execute();
JavaScript:<script>function onClientLoad() {
var ROOT = 'https://re-gifter.appspot.com/_ah/api';
gapi.client.load('regifter', 'v1', onLoad, ROOT);
Generate client libraries
gapi.client.load('regifter', 'v1', onLoad, ROOT);
}
function onLoad() {
gapi.client.regifter.gifts.insert('[email protected]', {giver: '[email protected]',
description: 'Fusilly Jerry'}).execute(onGiftInserted);
}
function onGiftInserted(response) {
alert('Gift given!');
}
</script>
<script src="https://apis.google.com/js/client.js?onload=onClientLoad"></script>
Add Authentication
• Create API Projecto https://code.google.com/apis/console
• Specify client ids• Specify client idso Browser, Server, Android, iOS
• Modify annotations
• Add User parameter to authorized methods
@Api(name = "regifter", version = "v1", clientIds = Constant.API_EXPLORER_CLIENT_ID)public class Regifter {
@ApiMethod(name = "gifts.list", path = "receiver/me/gift", httpMethod = HttpMethod.GET)public List<Gift> findGiftsByRecipient(User user) {
Key recipientKey = KeyFactory.createKey("Person", user.getEmail());return ofy().load().type(Gift.class).ancestor(recipientKey).list();
}
@ApiMethod(name = "gifts.insert", path = "receiver/me/gift", httpMethod = HttpMethod.POST)public Gift insertGift(User user, Gift gift) {
Key recipientKey = KeyFactory.createKey("Person", user.getEmail());gift.setRecipient(recipientKey);
Add authentication
gift.setRecipient(recipientKey);ofy().save().entity(gift).now();return gift;
}
}
Add authentication
ConclusionConclusionPros and Cons
The good parts
• Simple
• Minimal configuration
• Lightweight• Lightweight
• Integrated services
• No server maintenance
• Deploy in seconds
• Scalability implied
Drawbacks and limitations
• Experimentalo Bugs
o Breaking changes
•• Unintuitive error reporting in GPE
• Cold start delay
• Framework integration discouraged
• Third party libraries
• No custom domain for endpointso CORS browser support when using REST
• SQL support experimental
• JDO/JPA incompleteo no many-to-many owned relationships
o no join, aggregation, polymorphic queries
• Data migrationo Immutable keys
o New indices affect only new entities
Drawbacks and limitations
Thank you!Thank you!Questions?