Hypermedia APIs for headless
platforms and Data Integration
David Gómez G.
@dgomezg
Why are APIs important?
One user, many devices, multiple apps
APIs enable multiple consumers
@dgomezg@liferayeng
Why do WE need APIs?
API
Backend
Frontends
Liferay APIs in 2018
SOAP-WS
JSONWS API
REST/JAX-RS
A Simple JAX-RS Service Endpoint.
@GET @Path("/blogs/{id}")
@Produces(APPLICATION_JSON)
public Response getBlogsEntry(long id) {
BlogsEntry blogsEntry = _blogsEntryService.getEntry(id);
return Response.ok(blogsEntry).build();
}
JAX-RS Service Building Blocks
‣ Application
‣ Resources
‣ Extensions
Application (Mandatory)
@Component
@ApplicationPath(“api")
public class RecipesApplication extends Application {
public Set<Class<?>> getSingletons() {
return Collections.singleton(new RecipeResource());
}
}
‣ Manages the API resources
@dgomezg@liferayeng
Resources
@Path("recipe")
public class RecipeResource {
@GET @Path("{id}")
public RecipeDTO retrieveRecipe(@PathParam("id") long id) {
return new RecipeDTO(_recipeService.getRecipe(id).getName());
}
@Reference
private RecipeService _recipeService;
}
‣ Manages the endpoints in a specific Path
Data Objects
@XmlRootElement
public class RecipeDTO {
@XmlElement
public List<String> getSteps() {
return steps;
}
@XmlElement
public long getId() {
return id;
}
‣ Annotated with binding info
Data Objects
public class RecipeDTO {
@JsonGetter(value = "steps")
public List<String> getSteps() {
return steps;
}
@JsonGetter(value = “id")
public long getId() {
return id;
}
‣ Annotated with binding info
Extensions
@PreMatching
public class AuthFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) throws IOException {
String authHeader = ctx.getHeaderString(HttpHeaders.AUTHORIZATION);
if (!verifyUser(authHeader)) {
throw new NotAuthorizedException("Not Authorized!");
}
}
private boolean verifyUser(String authHeader) {}
}
‣ Adds additional logic to the API
JAX-RS & OSGi
JAX-RS Whiteboard
‣ Aries JAX-RS whiteboard
‣ OSGi JAX-RS Whiteboard Specification RI
‣ Component-property based.
Thanks to @csierra & @rotty3000 !!!!
JAX-RS Whiteboard
‣ Component property based
@Component(
property = {
"osgi.jaxrs.application.select=(osgi.jaxrs.name=recipes-application)",
“osgi.jaxrs.resource=true"
},
service=Object.class)
@Path("recipe")
public class RecipeResource {
@GET @Path("{id}")
public RecipeDTO retrieveRecipe(@PathParam("id") long id) throws PortalException {
/* ... */
}
}
Plain JSON result
[
{
"address": {
"countryName": "united-states",
"regionName": "Alabama",
"street": "69108 Murphy Lights",
"zipCode": 11839
},
"chefId": 34212,
"id": 34203,
"logoURL": "/image/organization_logo?img_id=34209&t=1542808587173",
"name": "Chopping Victory"
}
]
Challenges of a Headless platform
Discoverable &
Documented
Consumption
Helpers
APIs Security
Mechanisms
Are we capable of
solving these
challenges?
Is REST still a valid
solution?
The Web is still a case of success
On the other side
REST style is an abstraction of
the architectural elements within
a distributed hypermedia system
-- Roy Fielding, 2000
Hypermedia Controls
Consumers must only know
ONE URL
And how to navigate from it
Contract with consumer defines
affordance types
(relations, actions, …)
Start with IANA’s 80 relation types
Home URL Link TypesAffordance Types
Home URL
$ curl -H "Accept: application/json-home" 
http://localhost:8080/o/api
{
"_links": {
"self": {
"href": "https://apiosample.wedeploy.io"
},
"blog-postings": {
"href": "https://apiosample.wedeploy.io/p/blog-postings"
},
"people": {
"href": "https://apiosample.wedeploy.io/p/people"
}
}
}
JSON-HOME
How to use it?
[
{
"address": {
"countryName": "united-states",
"regionName": "Alabama",
"street": "69108 Murphy Lights",
"zipCode": 11839
},
"chefId": 34212,
"id": 34203,
"logoURL": “...”,
"name": "Chopping Victory"
}
]
{

"_embedded": { /* Here, our RecipeDTO serialised in JSON */ },

"total": 43,

"count": 30,

"_links": {

"first": {

"href": "http://localhost:8080/o/api/p/person?page=1&per_page=30"

},

"next": {

"href": "http://localhost:8080/o/api/p/person?page=2&per_page=30"

},

"last": {

"href": "http://localhost:8080/o/api/p/person?page=2&per_page=30"

}

}

}
HAL
Pagination
Affordance Types
{

"_embedded": {/* Here, our RecipeDTO serialised in JSON */},

"total": 43,

"count": 30,

"_links": {

"first": {

"href": "http://localhost:8080/o/api/p/groups?page=1&per_page=30"

},

"next": {

"href": "http://localhost:8080/o/api/p/groups?page=2&per_page=30"

},

"last": {

"href": "http://localhost:8080/o/api/p/groups?page=2&per_page=30"

}

}

}
HAL
Pagination
Affordance Types
Defined by 

IANA Link Relations
Pagination
Affordance Types
{

"properties" : { /* Here, our RecipeDTO serialised in JSON */}, 

"actions": [

{

"name": "delete-item",

"title": "Delete recipe",

"method": "DELETE",

"href": "http://localhost:8080/o/p/recipe/abcdef",

}

{

"name": "publish",

"title": "Publish recipe",

"method": "POST",

"href": “http://localhost:8080/o/p/recipe/123URLs4123AREabcdeOPAQUEf",

}
…
Actions
SIREN
Affordance Types
{

..., 

"operation": [

{

"method": "DELETE",

"@id": "_:person/delete",

"@type": "Operation"

},

{

"expects": "http://localhost:8080/o/api/f/u/recipe",

"method": "PUT",

"@id": “_:recipe/update",

"@type": "Operation"

}

],
...
Actions
JSON+LD
Affordance Types
Operations
Affordance Types
Fields
{

...

"actions": [

{

"name": "add-recipe",

"title": "Add a new recipe",

"method": "POST",

"href": "http://localhost:8080/o/p/recipe",

"type": "application/json",

"fields": [

{ "name": “name", "type": "text" },

{ "name": "Chef", "type": "Person" },

]

}
…
SIREN
Affordance Types
{

..., 

"operation": [

{

"method": "DELETE",

"@id": "_:recipe/delete",

"@type": "Operation"

},

{

"expects": “http://localhost:8080/o/api/f/u/recipe",

"method": "PUT",

"@id": "_:recipe/update",

"@type": "Operation"

}

],
...
From Actions...
JSON+LD
Affordance Types
{

"@id": "http://localhost:8080/o/api/f/u/recipe",

"title": "The recipe updater form",

"description": "This form can be used to update a recipe",

"supportedProperty": [

{

"@type": "SupportedProperty",

"property": "alternateName",

"readable": false,

"required": false,

"writeable": true

},

{

"@type": "SupportedProperty",

"property": “password”,

"readable": false,

"required": true,

"writeable": true

},

...To Forms
JSON+LD
Affordance Types
Shared Vocabularies
Standard types
schema.org: 597 types y 867
properties
ActivityStreams, microformats,
…
Never expose internal models
Custom types must be
consumer focused
Well defined custom types
Schema.org
Inheritance-based
model
All attributes are
optional
Standard types
How to use it?
[
{
"address": {
"countryName": "united-states",
"regionName": "Alabama",
"street": "69108 Murphy Lights",
"zipCode": 11839
},
"chefId": 34212,
"id": 34203,
"logoURL": “...”,
"name": "Chopping Victory"
}
]
JSONWS API &
Plain JAX-RS
Richardson Maturity Model - Martin Fowler
Climbing the ladder easily
(Our recipes with Apio)
Apio Components
‣ Representor
‣ Actions
‣ Permisions
‣ Affordances
Representor Pattern
https://www.flickr.com/photos/xwl/4936781806/
Hypermedia formats
{

"gender": "female",

"familyName": "Hart",

"givenName": "Sophia",

"jobTitle": "Senior Executive",

"name": "Sophia Hart",

"alternateName": "sophia.hart",

"birthDate": "1965-04-12T00:00Z",

"email": "sophia.hart@example.com",

"_links": {

"self": {

"href": "http://host/o/api/p/people/30723"

}

}

}
{

"class": "BlogPosting",

"properties": {

"headline": "Hello DEVCON!",

"content": "The content of this blog posting"

},

"actions": [

{

"name": "delete-blog-posting",

"title": "Delete Blog Posting",

"method": "DELETE",

"href": "http://localhost:8080/o/p/blogs/32400"

}

],

"links": [

{

"rel": [ "self" ],

"href": "http://localhost:8080/o/p/blogs/32400"

},

]

}
{

"gender": "female",

"familyName": "Hart",

"givenName": "Sophia",

"jobTitle": "Senior Executive",

"name": "Sophia Hart",

"alternateName": "sophia.hart",

"birthDate": "1965-04-12T00:00Z",

"email": "sophia.hart@example.com",

"@id": "http://localhost:8080/o/api/p/people/30723",

"@type": [

"Person"

],

"@context": {

"@vocab": "http://schema.org"

}

}
SIREN
JSON+LD
HAL
Content Negotiation
Accept: application/ld+json
Content-Type: application/ld+json
Accept: application/vnd.siren+json
Content-Type: application/
vnd.siren+json
Accept: application/hal+json
Content-Type: application/
hal+json
Representor +
ContentNegotiation
Creating Types (I)
@Type(“Restaurant")
public interface RestaurantType extends Identifier<Long> {
@Id
public long getId();
@Field(“chefId”) @LinkedModel(PersonType.class)
public Long getChefId();
@Field("name")
public String getName();
@Field("logoUrl")
public String getLogoURL();
@Field("address")
public PostalAddressType getAddress();
}
Making DTO implement Types
@Type(“Restaurant")
public class OrganizationDTO implements RestaurantType {
}
And get rid of all those JAXB or Jackson annotations!!!
Representor Pattern
https://www.flickr.com/photos/xwl/4936781806/
With the Representor Pattern internal models are
transformed to types and stored as a generic
representation that can be then mapped
to the representation format chosen by the user
Apio Componentes
‣ Representor
‣ Actions
‣ Permisions
‣ Affordances
ActionRouter
@Component
public class RestaurantActionRouter implements ActionRouter<RestaurantType> {
@Retrieve
@EntryPoint
public List<RestaurantType> retrieve(User user) throws PortalException {
List<Organization> organizations =
_organizationService.getUserOrganizations(user.getUserId());
return organizations.stream()
.map(OrganizationDTO::new)
.collect(Collectors.toList());
}
}
Action (no URL mapping)EntryPoint
(appears in the Home URL)
ActionRouter
ActionRouter action annotations
import com.liferay.apio.architect.annotation.Actions.*;
@Create
@Remove
@Replace
@Retrieve
@EntryPoint
Method mapping annotations
@Remove
public void remove(@Id long id) {...}
@Id indicates an Identifier
@Body parses the body content
@Create
public BlogPostiong create(@Body BlogPosting blogPosting) {...}
@ParentId links to the Id of a parent resource
@Retrieve
public List<BlogPosting> retrieveBlogPosts(
@ParentId(Organization.class) long groupId)
Special context parameters
@Retrieve
public List<BlogPosting> retrieveBlogPosts(
@ParentId(Organization.class) long groupId, User user)
User extracted from Context, logged User
Pagination & PageItems to handle pagination
@EntryPoint @Retrieve
public PageItems<BlogPosting> retrievePage(Pagination pagination)
A more complex ActionRouter
@Component
public class BlogPostingActionRouter implements ActionRouter<BlogPosting> {
@Create
public BlogPosting create(@Body BlogPosting blogPosting) {...}
@Remove
public void remove(@Id long id) {...}
@Replace
public BlogPosting replace(@Id long id, @Body BlogPosting blogPosting) {...}
@Retrieve
public BlogPosting retrieve(@Id long id) {...}
@EntryPoint @Retrieve
public PageItems<BlogPosting> retrievePage(Pagination pagination) {return null;}
@Subscribe
public BlogSubscription subscribe(@Id long id, @Body BlogSubscription subscrption){...}
Resource embedding and Sparse field sets
http://localhost:8080/o/api/restaurant
?embedded=creator
&fields[Person]=name,image
&sort=datePublished:desc
&filter=name eq ‘Chopping Victory’
&page=1
&per_page=10
Search, Sort and filter (Affordances w URL templates)
http://localhost:8080/o/api/restaurant
?embedded=creator
&fields[Person]=name,image
&sort=datePublished:desc
&filter=name eq ‘Chopping Victory’
&page=1
&per_page=10
Pagination
http://localhost:8080/o/api/restaurant
?embedded=creator
&fields[Person]=name,image
&sort=datePublished:desc
&filter=name eq ‘Chopping Victory’
&page=1
&per_page=10
More out-of-the-box features
● Bean Validation (JSR 330) support
● Open API generated
● Discoverability from Home URL
● Vocabulary
Key scenarios
P a b l o A g u l l a
1
Backend of a
custom frontend
2
Enabler of
omnichannel
experiences
3
Data integration &
platform
management
P a b l o A g u l l a
3
Data integration &
platform
management
Data integration and platform administration
Other sources
Liferay JSON
schema
Headless APIs
Content Management API
Management
UI
Liferay Commerce
CRM
Navigability & auto-discovery
Final Lessons!|
Spend time defining your vocabulary
It is the most important design activity
for an API
Make consumers & their
developers the focus of your
API design strategy
● Provide features that make their job easier
● APIs should speak their language, not yours
Enable clients and tools to
discover and navigate your API
● Same code could work for different scenarios
Thanks
David Gómez G.
@dgomezg

David Gómez G. - Hypermedia APIs for headless platforms and Data Integration - Codemotion Milan 2018

  • 1.
    Hypermedia APIs forheadless platforms and Data Integration David Gómez G. @dgomezg
  • 2.
    Why are APIsimportant?
  • 4.
    One user, manydevices, multiple apps
  • 5.
    APIs enable multipleconsumers @dgomezg@liferayeng
  • 6.
    Why do WEneed APIs?
  • 9.
  • 10.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
    A Simple JAX-RSService Endpoint. @GET @Path("/blogs/{id}") @Produces(APPLICATION_JSON) public Response getBlogsEntry(long id) { BlogsEntry blogsEntry = _blogsEntryService.getEntry(id); return Response.ok(blogsEntry).build(); }
  • 19.
    JAX-RS Service BuildingBlocks ‣ Application ‣ Resources ‣ Extensions
  • 20.
    Application (Mandatory) @Component @ApplicationPath(“api") public classRecipesApplication extends Application { public Set<Class<?>> getSingletons() { return Collections.singleton(new RecipeResource()); } } ‣ Manages the API resources @dgomezg@liferayeng
  • 21.
    Resources @Path("recipe") public class RecipeResource{ @GET @Path("{id}") public RecipeDTO retrieveRecipe(@PathParam("id") long id) { return new RecipeDTO(_recipeService.getRecipe(id).getName()); } @Reference private RecipeService _recipeService; } ‣ Manages the endpoints in a specific Path
  • 22.
    Data Objects @XmlRootElement public classRecipeDTO { @XmlElement public List<String> getSteps() { return steps; } @XmlElement public long getId() { return id; } ‣ Annotated with binding info
  • 23.
    Data Objects public classRecipeDTO { @JsonGetter(value = "steps") public List<String> getSteps() { return steps; } @JsonGetter(value = “id") public long getId() { return id; } ‣ Annotated with binding info
  • 24.
    Extensions @PreMatching public class AuthFilterimplements ContainerRequestFilter { @Override public void filter(ContainerRequestContext ctx) throws IOException { String authHeader = ctx.getHeaderString(HttpHeaders.AUTHORIZATION); if (!verifyUser(authHeader)) { throw new NotAuthorizedException("Not Authorized!"); } } private boolean verifyUser(String authHeader) {} } ‣ Adds additional logic to the API
  • 25.
  • 26.
    JAX-RS Whiteboard ‣ AriesJAX-RS whiteboard ‣ OSGi JAX-RS Whiteboard Specification RI ‣ Component-property based. Thanks to @csierra & @rotty3000 !!!!
  • 27.
    JAX-RS Whiteboard ‣ Componentproperty based @Component( property = { "osgi.jaxrs.application.select=(osgi.jaxrs.name=recipes-application)", “osgi.jaxrs.resource=true" }, service=Object.class) @Path("recipe") public class RecipeResource { @GET @Path("{id}") public RecipeDTO retrieveRecipe(@PathParam("id") long id) throws PortalException { /* ... */ } }
  • 28.
    Plain JSON result [ { "address":{ "countryName": "united-states", "regionName": "Alabama", "street": "69108 Murphy Lights", "zipCode": 11839 }, "chefId": 34212, "id": 34203, "logoURL": "/image/organization_logo?img_id=34209&t=1542808587173", "name": "Chopping Victory" } ]
  • 29.
    Challenges of aHeadless platform Discoverable & Documented Consumption Helpers APIs Security Mechanisms
  • 30.
    Are we capableof solving these challenges?
  • 31.
    Is REST stilla valid solution?
  • 32.
    The Web isstill a case of success On the other side
  • 33.
    REST style isan abstraction of the architectural elements within a distributed hypermedia system -- Roy Fielding, 2000
  • 35.
    Hypermedia Controls Consumers mustonly know ONE URL And how to navigate from it Contract with consumer defines affordance types (relations, actions, …) Start with IANA’s 80 relation types Home URL Link TypesAffordance Types
  • 36.
    Home URL $ curl-H "Accept: application/json-home" http://localhost:8080/o/api { "_links": { "self": { "href": "https://apiosample.wedeploy.io" }, "blog-postings": { "href": "https://apiosample.wedeploy.io/p/blog-postings" }, "people": { "href": "https://apiosample.wedeploy.io/p/people" } } } JSON-HOME
  • 37.
    How to useit? [ { "address": { "countryName": "united-states", "regionName": "Alabama", "street": "69108 Murphy Lights", "zipCode": 11839 }, "chefId": 34212, "id": 34203, "logoURL": “...”, "name": "Chopping Victory" } ]
  • 38.
    {
 "_embedded": { /*Here, our RecipeDTO serialised in JSON */ },
 "total": 43,
 "count": 30,
 "_links": {
 "first": {
 "href": "http://localhost:8080/o/api/p/person?page=1&per_page=30"
 },
 "next": {
 "href": "http://localhost:8080/o/api/p/person?page=2&per_page=30"
 },
 "last": {
 "href": "http://localhost:8080/o/api/p/person?page=2&per_page=30"
 }
 }
 } HAL Pagination Affordance Types
  • 39.
    {
 "_embedded": {/* Here,our RecipeDTO serialised in JSON */},
 "total": 43,
 "count": 30,
 "_links": {
 "first": {
 "href": "http://localhost:8080/o/api/p/groups?page=1&per_page=30"
 },
 "next": {
 "href": "http://localhost:8080/o/api/p/groups?page=2&per_page=30"
 },
 "last": {
 "href": "http://localhost:8080/o/api/p/groups?page=2&per_page=30"
 }
 }
 } HAL Pagination Affordance Types Defined by 
 IANA Link Relations
  • 40.
  • 41.
    {
 "properties" : {/* Here, our RecipeDTO serialised in JSON */}, 
 "actions": [
 {
 "name": "delete-item",
 "title": "Delete recipe",
 "method": "DELETE",
 "href": "http://localhost:8080/o/p/recipe/abcdef",
 }
 {
 "name": "publish",
 "title": "Publish recipe",
 "method": "POST",
 "href": “http://localhost:8080/o/p/recipe/123URLs4123AREabcdeOPAQUEf",
 } … Actions SIREN Affordance Types
  • 42.
    {
 ..., 
 "operation": [
 {
 "method":"DELETE",
 "@id": "_:person/delete",
 "@type": "Operation"
 },
 {
 "expects": "http://localhost:8080/o/api/f/u/recipe",
 "method": "PUT",
 "@id": “_:recipe/update",
 "@type": "Operation"
 }
 ], ... Actions JSON+LD Affordance Types
  • 43.
  • 44.
    Fields {
 ...
 "actions": [
 {
 "name": "add-recipe",
 "title":"Add a new recipe",
 "method": "POST",
 "href": "http://localhost:8080/o/p/recipe",
 "type": "application/json",
 "fields": [
 { "name": “name", "type": "text" },
 { "name": "Chef", "type": "Person" },
 ]
 } … SIREN Affordance Types
  • 45.
    {
 ..., 
 "operation": [
 {
 "method":"DELETE",
 "@id": "_:recipe/delete",
 "@type": "Operation"
 },
 {
 "expects": “http://localhost:8080/o/api/f/u/recipe",
 "method": "PUT",
 "@id": "_:recipe/update",
 "@type": "Operation"
 }
 ], ... From Actions... JSON+LD Affordance Types
  • 46.
    {
 "@id": "http://localhost:8080/o/api/f/u/recipe",
 "title": "Therecipe updater form",
 "description": "This form can be used to update a recipe",
 "supportedProperty": [
 {
 "@type": "SupportedProperty",
 "property": "alternateName",
 "readable": false,
 "required": false,
 "writeable": true
 },
 {
 "@type": "SupportedProperty",
 "property": “password”,
 "readable": false,
 "required": true,
 "writeable": true
 },
 ...To Forms JSON+LD Affordance Types
  • 47.
    Shared Vocabularies Standard types schema.org:597 types y 867 properties ActivityStreams, microformats, … Never expose internal models Custom types must be consumer focused Well defined custom types
  • 48.
  • 49.
    How to useit? [ { "address": { "countryName": "united-states", "regionName": "Alabama", "street": "69108 Murphy Lights", "zipCode": 11839 }, "chefId": 34212, "id": 34203, "logoURL": “...”, "name": "Chopping Victory" } ]
  • 50.
    JSONWS API & PlainJAX-RS Richardson Maturity Model - Martin Fowler
  • 51.
    Climbing the laddereasily (Our recipes with Apio)
  • 52.
    Apio Components ‣ Representor ‣Actions ‣ Permisions ‣ Affordances
  • 53.
  • 54.
    Hypermedia formats {
 "gender": "female",
 "familyName":"Hart",
 "givenName": "Sophia",
 "jobTitle": "Senior Executive",
 "name": "Sophia Hart",
 "alternateName": "sophia.hart",
 "birthDate": "1965-04-12T00:00Z",
 "email": "[email protected]",
 "_links": {
 "self": {
 "href": "http://host/o/api/p/people/30723"
 }
 }
 } {
 "class": "BlogPosting",
 "properties": {
 "headline": "Hello DEVCON!",
 "content": "The content of this blog posting"
 },
 "actions": [
 {
 "name": "delete-blog-posting",
 "title": "Delete Blog Posting",
 "method": "DELETE",
 "href": "http://localhost:8080/o/p/blogs/32400"
 }
 ],
 "links": [
 {
 "rel": [ "self" ],
 "href": "http://localhost:8080/o/p/blogs/32400"
 },
 ]
 } {
 "gender": "female",
 "familyName": "Hart",
 "givenName": "Sophia",
 "jobTitle": "Senior Executive",
 "name": "Sophia Hart",
 "alternateName": "sophia.hart",
 "birthDate": "1965-04-12T00:00Z",
 "email": "[email protected]",
 "@id": "http://localhost:8080/o/api/p/people/30723",
 "@type": [
 "Person"
 ],
 "@context": {
 "@vocab": "http://schema.org"
 }
 } SIREN JSON+LD HAL
  • 55.
    Content Negotiation Accept: application/ld+json Content-Type:application/ld+json Accept: application/vnd.siren+json Content-Type: application/ vnd.siren+json Accept: application/hal+json Content-Type: application/ hal+json Representor + ContentNegotiation
  • 56.
    Creating Types (I) @Type(“Restaurant") publicinterface RestaurantType extends Identifier<Long> { @Id public long getId(); @Field(“chefId”) @LinkedModel(PersonType.class) public Long getChefId(); @Field("name") public String getName(); @Field("logoUrl") public String getLogoURL(); @Field("address") public PostalAddressType getAddress(); }
  • 57.
    Making DTO implementTypes @Type(“Restaurant") public class OrganizationDTO implements RestaurantType { } And get rid of all those JAXB or Jackson annotations!!!
  • 58.
    Representor Pattern https://www.flickr.com/photos/xwl/4936781806/ With theRepresentor Pattern internal models are transformed to types and stored as a generic representation that can be then mapped to the representation format chosen by the user
  • 59.
    Apio Componentes ‣ Representor ‣Actions ‣ Permisions ‣ Affordances
  • 60.
    ActionRouter @Component public class RestaurantActionRouterimplements ActionRouter<RestaurantType> { @Retrieve @EntryPoint public List<RestaurantType> retrieve(User user) throws PortalException { List<Organization> organizations = _organizationService.getUserOrganizations(user.getUserId()); return organizations.stream() .map(OrganizationDTO::new) .collect(Collectors.toList()); } } Action (no URL mapping)EntryPoint (appears in the Home URL) ActionRouter
  • 61.
    ActionRouter action annotations importcom.liferay.apio.architect.annotation.Actions.*; @Create @Remove @Replace @Retrieve @EntryPoint
  • 62.
    Method mapping annotations @Remove publicvoid remove(@Id long id) {...} @Id indicates an Identifier @Body parses the body content @Create public BlogPostiong create(@Body BlogPosting blogPosting) {...} @ParentId links to the Id of a parent resource @Retrieve public List<BlogPosting> retrieveBlogPosts( @ParentId(Organization.class) long groupId)
  • 63.
    Special context parameters @Retrieve publicList<BlogPosting> retrieveBlogPosts( @ParentId(Organization.class) long groupId, User user) User extracted from Context, logged User Pagination & PageItems to handle pagination @EntryPoint @Retrieve public PageItems<BlogPosting> retrievePage(Pagination pagination)
  • 64.
    A more complexActionRouter @Component public class BlogPostingActionRouter implements ActionRouter<BlogPosting> { @Create public BlogPosting create(@Body BlogPosting blogPosting) {...} @Remove public void remove(@Id long id) {...} @Replace public BlogPosting replace(@Id long id, @Body BlogPosting blogPosting) {...} @Retrieve public BlogPosting retrieve(@Id long id) {...} @EntryPoint @Retrieve public PageItems<BlogPosting> retrievePage(Pagination pagination) {return null;} @Subscribe public BlogSubscription subscribe(@Id long id, @Body BlogSubscription subscrption){...}
  • 65.
    Resource embedding andSparse field sets http://localhost:8080/o/api/restaurant ?embedded=creator &fields[Person]=name,image &sort=datePublished:desc &filter=name eq ‘Chopping Victory’ &page=1 &per_page=10
  • 66.
    Search, Sort andfilter (Affordances w URL templates) http://localhost:8080/o/api/restaurant ?embedded=creator &fields[Person]=name,image &sort=datePublished:desc &filter=name eq ‘Chopping Victory’ &page=1 &per_page=10
  • 67.
  • 68.
    More out-of-the-box features ●Bean Validation (JSR 330) support ● Open API generated ● Discoverability from Home URL ● Vocabulary
  • 69.
  • 70.
    P a bl o A g u l l a 1 Backend of a custom frontend 2 Enabler of omnichannel experiences 3 Data integration & platform management
  • 71.
    P a bl o A g u l l a 3 Data integration & platform management
  • 72.
    Data integration andplatform administration Other sources Liferay JSON schema Headless APIs Content Management API Management UI Liferay Commerce CRM
  • 77.
  • 78.
  • 79.
    Spend time definingyour vocabulary It is the most important design activity for an API
  • 80.
    Make consumers &their developers the focus of your API design strategy ● Provide features that make their job easier ● APIs should speak their language, not yours
  • 81.
    Enable clients andtools to discover and navigate your API ● Same code could work for different scenarios
  • 82.