DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • GraphQL With Java Spring Boot and Postgres or MySQL Made Easy!
  • Providing Enum Consistency Between Application and Data
  • Testcontainers With Kotlin and Spring Data R2DBC
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS

Trending

  • Go 1.24+ Native FIPS Support for Easier Compliance
  • The Role of AI in Identity and Access Management for Organizations
  • Exploring Intercooler.js: Simplify AJAX With HTML Attributes
  • Implementing API Design First in .NET for Efficient Development, Testing, and CI/CD
  1. DZone
  2. Coding
  3. Languages
  4. Building an Amazon-Like Recommendation Engine Using Slash GraphQL

Building an Amazon-Like Recommendation Engine Using Slash GraphQL

Get started using Dgraph's Slash GraphQL product and connect to a Spring Boot application which will act as a simple RESTful recommendation service

By 
John Vester user avatar
John Vester
DZone Core CORE ·
Oct. 05, 20 · Tutorial
Likes (12)
Comment
Save
Tweet
Share
112.4K Views

Join the DZone community and get the full member experience.

Join For Free

Back in the early 2000s, I was working on a project implementing an eCommerce solution by Art Technology Group (ATG), now owned by Oracle. The ATG Dynamo product was an impressive solution as it included a persistence layer and a scenarios module. At the time, companies like Target and Best Buy used the Dynamo solution, leveraging the scenario module to provide recommendations to the customer.

As an example, the Dynamo eCommerce solution was smart enough to remember when a customer added a product to their cart and later removed it. As an incentive, the scenario server could be designed to offer the same customer a modest discount on a future visit if they re-added the product into their cart and purchased it within the next 24 hours.

Since those days, I have always wanted to create a simple recommendations engine, which is my goal for this publication.

About the Recommendations Example

I wanted to keep things simple and create some basic domain objects for the recommendations engine.  In this example, the solution will make recommendations for musical artists and the underlying Artist object is quite simple:

Java
 




x


 
1
@AllArgsConstructor
2
@NoArgsConstructor
3
@Data
4
public class Artist {
5
   private String name;
6
}



In a real system, there would be so many more attributes to track. However, in this example, the name of the artist will suffice.

As one might expect, customers will rate artists on a scale of 1 to 5, where a value of five represents the best score possible.  Of course, it is possible (and expected) that customers will not rate every artist.  The customer will be represented (again) by a very simple Customer object:

Java
 




xxxxxxxxxx
1


 
1
@AllArgsConstructor
2
@NoArgsConstructor
3
@Data
4
public class Customer {
5
   private String username;
6
}



The concept of a customer rating an artist will be captured in the following Rating object:

Java
 




xxxxxxxxxx
1


 
1
@AllArgsConstructor
2
@NoArgsConstructor
3
@Data
4
public class Rating {
5
   private String id;
6
   private double score;
7
   private Customer by;
8
   private Artist about;
9
}



In my normal Java programming efforts I would likely use private Customer customer and private Artist artist for my objects, but I wanted to follow the pattern employed by graph databases, where I employ variables like by and about instead.  This should become more clear as the article continues.

Dgraph Slash GraphQL

With the popularity of graph databases, I felt like my exploration of creating a recommendations engine should employ a graph database. After all, GraphQL has become a popular language for talking to services about graphs. While I only have some knowledge around graph databases, my analysis seemed to conclude that a graph database is the right choice for this project and is often the source for real-world services making recommendations. Graph databases are a great solution when the relationships (edges) between your data (nodes) are just as important as the data itself — and a recommendation engine is the perfect example.  

However, since I'm just starting out with graph databases, I certainly didn't want to worry about starting up a container or running a GraphQL database locally.  Instead I wanted to locate a SaaS provider. I decided to go with Dgraph's fully-managed backend service, called Slash GraphQL. It's a hosted, native GraphQL solution. The Slash GraphQL service was just released on September 10th, 2020, and includes a free 10,000 credits service, which can be enabled by using the following link:

https://slash.dgraph.io/

After launching this URL, a new account can be created using the normal authorization services:

Slash Graph authentication

In my example, I created a backend called "spring-boot-demo" which ultimately resulted in the following dashboard:

graphql dashboard

The process to get started was quick and free, making it effortless to configure the Slash GraphQL service.

Configuring Slash GraphQL

As with any database solution, we must write a schema and deploy it to the database.  With Slash GraphQL, this was quick and easy:

Plain Text
 




xxxxxxxxxx
1
16


 
1
type Artist {
2
   name: String! @id @search(by: [hash, regexp])
3
   ratings: [Rating] @hasInverse(field: about)
4
}
5

          
6
type Customer {
7
   username: String! @id @search(by: [hash, regexp])
8
   ratings: [Rating] @hasInverse(field: by)
9
}
10

          
11
type Rating {
12
   id: ID!
13
   about: Artist!
14
   by: Customer!
15
   score: Int @search
16
}



In fact, my original design was far more complex than it needed to be, and the level of effort behind making revisions was far easier than I expected. I quickly began to see the value as a developer to be able to alter the schema without much effort.

With the schema in place, I was able to quickly populate some basic Artist information:

JSON
 




xxxxxxxxxx
1
14


 
1
mutation {
2
 addArtist(input: [
3
   {name: "Eric Johnson"},
4
   {name: "Genesis"},
5
   {name: "Led Zeppelin"},
6
   {name: "Rush"},
7
   {name: "Triumph"},
8
   {name: "Van Halen"},
9
   {name: "Yes"}]) {
10
   artist {
11
     name
12
   }
13
 }
14
}



At the same time, I added a few fictional Customer records:

JSON
 




xxxxxxxxxx
1
12


 
1
mutation {
2
 addCustomer(input: [
3
   {username: "Doug"},
4
   {username: "Jeff"},
5
   {username: "John"},
6
   {username: "Russell"},
7
   {username: "Stacy"}]) {
8
   customer {
9
     username
10
   }
11
 }
12
}



As a result, the five customers will provide ratings for the seven artists, using the following table:

seven artists in table

An example of the rating process is shown below:

JSON
 




xxxxxxxxxx
1
13


 
1
mutation {
2
 addRating(input: [{
3
   by: {username: "Jeff"},
4
   about: { name: "Triumph"},
5
   score: 4}])
6
 {
7
   rating {
8
     score
9
     by { username }
10
     about { name }
11
   }
12
 }
13
}



With the Slash GraphQL data configured and running, I can now switch gears and work on the Spring Boot service.

The Slope One Ratings Algorithm

In 2005, a research paper by Daniel Lemire and Anna Maclachian introduced the Slope One family of collaborative filtering algorithms. This simple form of item-based collaborative filtering looked to be a perfect fit for a recommendations service, because it takes into account ratings by other customers in order to score items not rated for a given customer.

In pseudo-code, the Recommendations Service would achieve the following objectives:

  • Retrieve the ratings available for all artists (by all customers).

  • Create a Map<Customer, Map<Artist, Double>> from the data, which is a customer map, containing all the artists and their ratings.

  • The rating score of 1 to 5 will be converted to a simple range between 0.2 (worst rating of 1) and 1.0 (best rating of 5).

With the customer map created, the core of the Slope One ratings processing will execute by calling the SlopeOne class:

  • Populate a Map<Artist, Map<Artist, Double>> used to track differences in ratings from one customer to another.

  • Populate a Map<Artist, Map<Artist, Integer>> used to track the frequency of similar ratings.

  • Use the existing maps to create a Map<Customer, HashMap<Artist, Double>> which contain projected ratings for items not rated for a given customer.

For this example, a random Customer is selected and the corresponding object from the Map<Customer, HashMap<Artist, Double>> projectedData map is analyzed to return the following results:

JSON
 




xxxxxxxxxx
1
25


 
1
{
2
   "matchedCustomer": {
3
       "username": "Russell"
4
   },
5
   "recommendationsMap": {
6
       "Artist(name=Eric Johnson)": 0.7765842245950264,
7
       "Artist(name=Yes)": 0.7661904474477843,
8
       "Artist(name=Triumph)": 0.7518039724158979,
9
       "Artist(name=Van Halen)": 0.7635436007978691
10
   },
11
   "ratingsMap": {
12
       "Artist(name=Genesis)": 0.4,
13
       "Artist(name=Led Zeppelin)": 1.0,
14
       "Artist(name=Rush)": 0.6
15
   },
16
   "resultsMap": {
17
       "Artist(name=Eric Johnson)": 0.7765842245950264,
18
       "Artist(name=Genesis)": 0.4,
19
       "Artist(name=Yes)": 0.7661904474477843,
20
       "Artist(name=Led Zeppelin)": 1.0,
21
       "Artist(name=Rush)": 0.6,
22
       "Artist(name=Triumph)": 0.7518039724158979,
23
       "Artist(name=Van Halen)": 0.7635436007978691
24
   }
25
}



In the example above, the "Russell" user was randomly selected. When looking at the original table (above), Russell only provided ratings for Genesis, Led Zeppelin, and Rush. The only artist that he truly admired was Led Zeppelin. This information is included in the ratingsMap object and also in the resultsMap object.

The resultsMap object includes projected ratings for the other four artists: Eric Johnson, Yes, Triumph, and Van Halen. To make things easier, there is a recommendationsMap included in the payload, which includes only the artists that were not rated by Russell.

Based upon the other reviews, the Recommendations Service would slightly favor Eric Johnson over the other four items—with a score of 0.78, which is nearly a value of four in the five-point rating system.

The Recommendations Service

In order to use the Recommendations Service, the Spring Boot server simply needs to be running and configured to connect to the Slash GraphQL cloud-based instance. The GraphQL Endpoint on the Slash GraphQL Dashboard can be specified in the application.yml as slash-graph-ql.hostname or via passing in the value via the ${SLASH_GRAPH_QL_HOSTNAME} environment variable.

The basic recommendations engine can be called using the following RESTful URI:

GET - {spring-boot-service-host-name}/recommend

This action is configured by the RecommendationsController, as shown below:

Java
 




xxxxxxxxxx
1


 
1
@GetMapping(value = "/recommend")
2
public ResponseEntity<Recommendation> recommend() {
3
   try {
4
       return new ResponseEntity<>(recommendationService.recommend(), HttpStatus.OK);
5
   } catch (Exception e) {
6
       return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
7
   }
8
}



Which calls the RecommendationService:

Java
 




xxxxxxxxxx
1
32


 
1
@Slf4j
2
@RequiredArgsConstructor
3
@Service
4
public class RecommendationService {
5
   private final ArtistService artistService;
6
   private final CustomerService customerService;
7
   private final SlashGraphQlProperties slashGraphQlProperties;
8

          
9
   private static final String RATING_QUERY = "query RatingQuery {"
10
         + "queryRating { "
11
         + "id, " 
12
         + "score, " 
13
         + "by { username }, " 
14
         + "about { name } " 
15
         + "}}";
16

          
17
   public Recommendation recommend() throws Exception {
18
       ResponseEntity<String> responseEntity = RestTemplateUtils.query(slashGraphQlProperties.getHostname(), RATING_QUERY);
19

          
20
      try {
21
           ObjectMapper objectMapper = new ObjectMapper();
22
           SlashGraphQlResultRating slashGraphQlResult = objectMapper.readValue(responseEntity.getBody(), SlashGraphQlResultRating.class);
23
        
24
           log.debug("slashGraphQlResult={}", slashGraphQlResult);
25
           return makeRecommendation(slashGraphQlResult.getData());
26
       } catch (JsonProcessingException e) {
27
           throw new Exception("An error was encountered processing responseEntity=" + responseEntity.getBody(), e);
28
       }
29
   }
30
...
31

          
32
}



Please note — something that might be missed at a quick glance of this code, is the power and ease in being able to pull out a subgraph to perform the recommendation. In the example above, the slashGraphQlResult.getData() line is providing a subgraph to the makeRecommendation() method.

The RATING_QUERY is the expected Slash GraphQL format to retrieve Rating objects.  The RestTemplateUtils.query() method is part of a static utility class, to keep things DRY (don't repeat yourself):

Java
 




xxxxxxxxxx
1
13


 
1
public final class RestTemplateUtils {
2
   private RestTemplateUtils() { }
3
   private static final String MEDIA_TYPE_GRAPH_QL = "application/graphql";
4
   private static final String GRAPH_QL_URI = "/graphql";
5

          
6
   public static ResponseEntity<String> query(String hostname, String query) {
7
       RestTemplate restTemplate = new RestTemplate();
8
       HttpHeaders headers = new HttpHeaders();
9
       headers.setContentType(MediaType.valueOf(MEDIA_TYPE_GRAPH_QL));
10
       HttpEntity<String> httpEntity = new HttpEntity<>(query, headers);
11
       return restTemplate.exchange(hostname + GRAPH_QL_URI, HttpMethod.POST, httpEntity, String.class);
12
   }
13
}



Once the slashGraphQlResult object is retrieved, the makeRecommendation() private method is called, which returns the following Recommendation object. (This was shown above in JSON format):

Java
 




xxxxxxxxxx
1


 
1
@AllArgsConstructor
2
@NoArgsConstructor
3
@Data
4
public class Recommendation {
5
   private Customer matchedCustomer;
6
   private HashMap<Artist, Double> recommendationsMap;
7
   private HashMap<Artist, Double> ratingsMap;
8
   private HashMap<Artist, Double> resultsMap;
9
}


Conclusion

In this article, an instance of Dgraph Slash GraphQL was created with a new schema and sample data was loaded.  That data was then utilized by a Spring boot service which served as a basic recommendations engine.  For those interested in the full source code, please review the GitLab repository.

From a cost perspective, I am quite impressed with the structure that Slash GraphQL provides.  The new account screen indicated that I have 10,000 credits to use, per month, for no charge.  In the entire time I used Slash GraphQL to prototype and create everything for this article, I only utilized 292 credits.  Current pricing for Slash GraphQL makes use of the service very attractive, at $9.99/month for 5GB of data transfer. No hidden costs. No costs for data storage. No cost per query.

Using a graph database for the first time did present a small learning curve and I am certain there is far more than I can learn by continuing this exercise.  I felt that Slash GraphQL exceeded my expectations with the ability to change the schema as I learned more about my needs.  As a feature developer, this is a very important aspect that should be recognized, especially compared to the same scenario with a traditional database.

In my next article, I'll introduce an Angular (or React) client into this process, which will interface directly with GraphQL and the Recommendation Service running in Spring Boot.

Have a really great day!

GraphQL Database Engine Spring Framework Object (computer science) Spring Boot Data (computing) Java (programming language) Graph (Unix)

Published at DZone with permission of John Vester, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • GraphQL With Java Spring Boot and Postgres or MySQL Made Easy!
  • Providing Enum Consistency Between Application and Data
  • Testcontainers With Kotlin and Spring Data R2DBC
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!