Create a Data Marvel — Part 5: Writing the Domain Classes

Jennifer Reif
Neo4j Developer Blog
8 min readJan 9, 2019

--

*Update*: All parts of this series are published and related content available.

Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, Part 8, Part 9, Part 10

Completed Github project (+related content)

Picking up from where we left off before the holiday season in Part 4 of this Marvel tech series, this post will show the code for our Marvel comic entities. As you may recall, our data model is structured with 6 entities, as shown in the image below.

At the end of the last post, we created the Spring Data Neo4j application using the start.spring.io site, opened the project in IntelliJ, and delved into the project structure. Now, we are ready to write some code, starting with our domain classes!

Domain classes

Just as we did when we loaded the data from the Marvel API, we will start with the domain entity classes. They are a smaller and simpler entrypoint to the rest, and the values are more familiar to even non-comic-book fans.

The Character class

We will create our domain class for characters in IntelliJ by right-mouse clicking on the demo folder (filepath: src->main->java->com->example->demo), mousing over the New option, and choosing the Java Class option.

Fill in the name you want for your class (in this case, Character), click OK, and IntelliJ generates some bare-bones structure for us, so we can take it from there.

One brief note on class names before we move on. Take care when naming your classes that they do not conflict with language keywords and types. For instance, the class Character caused some confusion with later code because there is also a Java character type. We noted the problem and easily avoided it, but it is something to keep in mind when you’re in full “domain definition mode”. If I would have given this a bit more thought before our project, I probably would have chosen something like ‘ComicCharacter’ instead.

The generated code includes just an outline of our class structure. We have the package path defined at the top for us and the (empty) class declaration beneath that.

Now we start adding some definition for our Marvel use case. The full code for this class is below. We will explain the syntax in the following paragraphs.

package com.example.demo;import @Data
@NoArgsConstructor
@RequiredArgsConstructor
@NodeEntity
public class Character {
@Id
@GeneratedValue
private Long neoId;
@NonNull
private Long id;
@NonNull
private String name, description, resourceURI, thumbnail;
}

We first want to add annotations for Lombok to shortcut some boilerplate like our getters/setters and constructors. The @Data annotation handles much of that boilerplate for the standard POJOs, supplying the getters, setters, and equals+hashcode+toString methods. Annotations @NoArgsConstructor and @RequiredArgsConstructor generate a constructor with no arguments and a constructor with one argument for each field required to be @NonNull, respectively. We also need to annotate with @NodeEntity to let Spring Data Neo4j (SDN) know that this is a domain class for a node in our graph database.

Within the class declaration, we start defining our member variables/fields for the database and the Marvel data. The first id field (neoId) is the internal id that Neo4j assigns to each of its nodes and relationships when it is stored. It is rarely used or referenced, but it is part of the entity in the database, so we assign the @Id and @GeneratedValue annotations to it since we do not handle the values.

The next id field (id) is the identifier from the Marvel API. This value allows us to retrieve any additional info from the API or reference an entity without translating for custom ids. We add the @NonNull annotation to ensure that the field always has a value and will not accept null. The last fields are String values from the API that we felt were the most meaningful to our application. The @NonNull annotation also applies here to ensure values are not missing.

The repository interface

Next, we will need to create a repository for Character objects for the data access layer to the database. Spring Data is designed with the flexibility to interface with different kinds of data stores, so the interaction with Neo4j should be similar to many of Spring Data’s other interface projects.

Just as we did to create the Character class, we can right-click on the demo folder in the project structure, choose New, then pick Java Class. When the box appears to type in the name, we will use CharacterRepo, then choose Interface as the Kind, rather than the Class type.

Click OK, and the interface should appear with some skeleton code generated. Again, the package path is defined for us, as well as the interface declaration.

The only thing we need to add here is to make our interface extend the Neo4jRepository interface, as shown below.

package com.example.demo;import org.springframework.data.neo4j.repository.Neo4jRepository;public interface CharacterRepo
extends Neo4jRepository<Character, Long> {
}

The Neo4jRepository interface provides Neo4j-specific implementation details on top of several extended Spring repositories as the foundation. Neo4jRepository requires two types to be specified — our class type and its id type. Once we add our Character and Long values here, we have finished our required code for this interface.

* Note: If we wanted to run some queries to return Character data specifically or view a character’s relationships, we could define methods and other details in this interface. However, for simplicity, we chose to define all those methods through one entity — the ComicIssue. Since we decided in an earlier step that our project only cared about data as it was related to the ComicIssue and made it the center of our graph data model, it made sense to focus our methods there.

The controller class

Finally, we need one more piece to complete our Character classes. We have our entity defined and our data access interface written. The controller acts as the messenger between the data layer and the user interface to accept requests from the user and send back responses. This is where the code for logic and data manipulation is typically placed. It coordinates different responses based on the kind of input it receives.

For our character controller, we add a new Java class like we did with the domain class (right-click on demo folder, choose New->Java Class). Type in the name CharacterController, keep the Kind value as Class, and click OK.

We will need to add a couple of annotations, as well as some methods to this class. The completed class code is below, and the explanation will follow.

package com.example.demo;import ….@RestController
@RequestMapping(“/characters”)
public class CharacterController {
private final CharacterRepo repo;
public CharacterController(CharacterRepo repo) {
this.repo = repo;
}
@GetMapping
public Iterable<Character> getAllCharacters() {
return repo.findAll();
}
}

The @RestController annotation combines the @Controller and @ResponseBody annotations, eliminating the need to put @ResponseBody on each request handling method. Our @RequestMapping annotation is used to map requests to a controller method for a certain path (in this case, /characters).

Within the class declaration, we begin by creating a local member variable for our CharacterRepo, and the next line, our constructor, injects that repository into our controller so that we can access the data layer. The @GetMapping annotation tells us this will be a GET method and precedes the method declaration for getAllCharacters() that accesses the repository, executes the provided findAll() method, and returns multiple Character entities (Iterable of type Character).

Wrapping up

Fortunately, this is all the code we need at this point to define our Character-related classes. This would be enough to hit the /characters endpoint (using the curl command or something similar) and return all the Character entities in our database.

What I Learned

I had built similar applications with Spring in the past, so this was pretty straightforward and consistent with my past experiences. Spring handles much of the boilerplate and nuances, so I could focus on the data and representation, rather than the setup code to get from the database to the user. It didn’t take too much additional research or effort to integrate with Neo4j. Helpful documentation and examples also shortened the learning curve, as well.

As always, the highlights of my learning experience in this step are listed below. :)

  1. The built-in capabilities of the Spring ecosystem help reduce boilerplate code and nearly eliminate the most rudimentary (and often boring) parts of the codebase.
  2. Spring Data provides a highly-flexible and simple interface to plug into a variety of data sources. It is designed to work seamlessly, whether you are working with relational, NoSQL, or graph databases. Consistent syntax helped me focus on the pieces of the code that were specific to Neo4j and the graph data model.
  3. Coding the Character-related classes first gave me an easier on-ramp because the code was simple and nearly mirrored previous applications with relational backends. Starting with a small piece of the application and not focusing (yet!) on the relationship component of the graph model allowed me to easily consume and better internalize any differences in smaller chunks.

Next Steps

Even with the little bit of code that we wrote today, we can now retrieve Character entities from Neo4j and display them (even if it is only in JSON format). :) In upcoming posts, we will continue adding pieces of code until we can retrieve and review all of our entities through a prettier interface!

Resources

--

--

Jennifer Reif
Neo4j Developer Blog

Jennifer Reif is an avid developer and problem-solver. She enjoys learning new technologies, sometimes on a daily basis! Her Twitter handle is @JMHReif.