Customizing field arguments in Brightspot Content Delivery API
There exist use cases where the default id and path arguments exposed in a Content Delivery API (CDA) do not fit the intended functionality. Fortunately, they are flexible!
Prerequisites
For endpoint configuration: Custom Content Delivery API development or Hello Content Delivery API.
Introduction
When exposing GraphQL APIs via Brightspot’s view system, data is typically queried for by ID or path. However, there are cases in which callers may want to query by some other criteria, like a third-party ID or maybe even remove the arguments altogether, leaving object resolution up to the view model.
Background
When it comes to removing the arguments completely, we can accomplish this by using the correct generic argument for the relevant ViewModel class. There are two ways to go about this:
- Leverage Brightspot’s Singleton interface on your view model’s model.
- Utilize your ContentDeliveryApiEndpoint subclass as your view model’s model.
For adding custom arguments we can leverage Brightspot’s @WebParameter annotation. It allows us to treat scalar fields, such as integers, strings, and even lists of them, as an argument.Additionally, we can create custom Java annotations that are utilized to denote a field that should be transformed into an argument with a custom input type, and even populate a Java POJO, by using ContentDeliveryApiWebAnnotationProcessor.
Examples
View model with singleton model
1public class Homepage extends Content implements Singleton {23private String title;45public String getTitle() {6return title;7}89public void setTitle(String title) {10this.title = title;11}12}1314@ViewInterface15public class HomePageViewModel extends ViewModel<Homepage> {1617public String getTitle() {18return model.getTitle();19}20}
The following types are generated in the resulting schema:
1type Query {2Homepage: Homepage3}45type Homepage {6title: String7}
Callers are now able to query for the homepage without any arguments since the system knows how to find the one and only.
API entry field
Sometimes API designers may not need to use a data model, or may want to implement their own logic to resolve the proper data model. In this case, they can just use their endpoint class as the model for the view model.
Also, as discussed earlier, they can leverage @WebParameter to expose their own scalar arguments.
Consider the following classes:
1public class Article extends Content {23private String headline;45public String getHeadline() {6return headline;7}89public void setHeadline(String headline) {10this.headline = headline;11}12}1314@ViewInterface15public class ArticleViewModel extends ViewModel<Article> {1617public String getHeadline() {18return model.getHeadline();19}20}2122@ViewInterface23public class ArticleSearchViewModel extends ViewModel<ArticleEndpoint> {2425@WebParameter26private String search;2728@WebParameter29private Integer limit = 5;3031public Iterable<ArticleViewModel> getArticles() {32Query<Article> articleQuery = Query.from(Article.class);3334if (!StringUtils.isBlank(search)) {35articleQuery.where("* matches ?", search);36}3738final int maxLimit = 50;3940return createViews(ArticleViewModel.class, articleQuery41.select(0, Math.min(limit, maxLimit)).getItems());42}43}4445public class ArticleEndpoint extends ContentDeliveryApiEndpoint {4647@Override48protected String getPathSuffix() {49return "/article";50}5152@Override53public List<ContentDeliveryEntryPointField> getQueryEntryFields() {54return Collections.singletonList(new ContentDeliveryEntryPointField(ArticleSearchViewModel.class));55}56}
The following types are generated in the resulting schema:
1type Query {2ArticleSearch(search: String, limit: Int): ArticleSearch3}45type ArticleSearch {6articles: [Article]7}89type Article {10headline: String11}
The @WebParameter annotated fields are assigned to their respective argument value passed in the GraphQL query, and are utilized in the view model logic. Callers are now able to query for articles via a general full-text search.
Custom input type
API designers may want to expose a structured input type and have it populate some custom type on the back end. This is easily achievable with a custom Java annotation and a ContentDeliveryApiWebAnntoationProcessor implementation.
Consider the following classes:
1public class Person {23private String name;45private String location;67public String getName() {8return name;9}1011public void setName(String name) {12this.name = name;13}1415public String getLocation() {16return location;17}1819public void setLocation(String location) {20this.location = location;21}22}2324@Documented25@Retention(RetentionPolicy.RUNTIME)26@Target(ElementType.FIELD)27public @interface CurrentPerson {2829}3031public class CurrentPersonProcessor implements ContentDeliveryApiWebAnnotationProcessor<CurrentPerson> {3233@Override34public Object getValue(ApiRequest request, Object input, Field field, CurrentPerson annotation) {35String name = (String) CollectionUtils.getByPath(input, "name");36String location = (String) CollectionUtils.getByPath(input, "location");3738Person person = new Person();39person.setName(name);40person.setLocation(location);4142return person;43}4445@Override46public SchemaInputType getInputType(Field field, CurrentPerson annotation) {47return SchemaInputTypes.newInputObject("Person")48.field("name", SchemaInputTypes.nonNullOf(SchemaInputTypes.STRING))49.field("location", SchemaInputTypes.nonNullOf(SchemaInputTypes.STRING))50.build();51}52}5354public class GreetingEndpoint extends ContentDeliveryApiEndpoint {5556@Override57protected String getPathSuffix() {58return "/greeting";59}6061@Override62public List<ContentDeliveryEntryPointField> getQueryEntryFields() {63return Collections.singletonList(new ContentDeliveryEntryPointField(GreetingViewModel.class));64}65}6667@ViewInterface68public class GreetingViewModel extends ViewModel<GreetingEndpoint> {6970@CurrentPerson71private Person person;7273public String getGreeting() {74return "Hello " + person.getName() + " from " + person.getLocation() + "!";75}76}
The following types are generated in the resulting schema:
1type Query {2Greeting(person: Person): Greeting3}45type Greeting {6greeting: String7}89input Person {10name: String!11location: String!12}
The @CurrentPerson annotated field is assigned to its respective argument value passed in the GraphQL query, and is utilized in the view model logic.