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
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
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
What's in store for DevOps in 2023? Hear from the experts in our "DZone 2023 Preview: DevOps Edition" on Fri, Jan 27!
Save your seat
  1. DZone
  2. Data Engineering
  3. Databases
  4. Building a Dating Site With Neo4j: Part 2

Building a Dating Site With Neo4j: Part 2

With our planning out of the way, let's dive into our endpoints and schema as we see how to use Neo4j to build a dating site.

Max De Marzi user avatar by
Max De Marzi
·
Aug. 13, 18 · Tutorial
Like (4)
Save
Tweet
Share
3.78K Views

Join the DZone community and get the full member experience.

Join For Free

We came up with an idea for a dating site and an initial model in Part One. Next, we are going to work on a back end HTTP API, because I’m old school and that’s the way I like it. We will build our HTTP API right into Neo4j using an extension which turns Neo4j from a Server into a Service. Unlike last time where we wrote a clone of Twitter, I don’t really know where I’m going with this, so let’s start with some of the obvious API endpoints and then we can design and build more as we go along. Is this Agile or am I just being an idiot? I can’t tell, so onward we go.

First obvious thing is, we need a schema. Luckily Neo4j is a “Schema Optional” database so we don’t have to worry about designing any tables or properties or figuring out what kind of properties each table will have. Because… well, we don’t have tables. The only real schema we need to worry about are Constraints and Indexes. For example, we don’t want two users to have the same username or same email, so we will create a uniqueness constraint on those Label-Property combinations. We also want our users to pick Attributes they have and Attributes they want in a potential mate. To keep things keep clean and help the matching, we will seed the database with some Attributes and not let the users create them dynamically. However they need to be able to find and search for these Attributes, so we will index their names. Well, we will index a lowercase version of their names since the current Neo4j schema indexes are CaSe SeNsItIve. So our schema endpoint could start like this:

@Path("/schema")
public class Schema {

        private static final ObjectMapper objectMapper = new ObjectMapper();

        @POST
        @Path("/create")
        public Response create(@Context GraphDatabaseService db) throws IOException {
            ArrayList<String> results = new ArrayList<>();
            try (Transaction tx = db.beginTx()) {
                org.neo4j.graphdb.schema.Schema schema = db.schema();
                if (!schema.getConstraints(Labels.Attribute).iterator().hasNext()) {
                    schema.constraintFor(Labels.Attribute)
                            .assertPropertyIsUnique(LOWERCASE_NAME)
                            .create();

                    tx.success();
                    results.add("(:Attribute {lowercase_name}) constraint created");
                }
            }

            try (Transaction tx = db.beginTx()) {
                org.neo4j.graphdb.schema.Schema schema = db.schema();
                if (!schema.getConstraints(Labels.User).iterator().hasNext()) {
                    schema.constraintFor(Labels.User)
                            .assertPropertyIsUnique(USERNAME)
                            .create();
                    schema.constraintFor(Labels.User)
                            .assertPropertyIsUnique(EMAIL)
                            .create();
                    tx.success();
                    results.add("(:User {username}) constraint created");
                    results.add("(:User {email}) constraint created");
                }
            }
            results.add("Schema Created");
            return Response.ok().entity(objectMapper.writeValueAsString(results)).build();
        }
}


Next, we need to be able to create Users and then fetch those users. So how about we add that so our API looks like:

:POST   /v1/schema/create
:GET    /v1/users/{username}
:POST   /v1/users


We will create a Users class with an ObjectMapper to convert our results to JSON at the end of each method. Our first endpoint gets users, so we need to pass in a username and use the Graph Database Service in the context of our query. Inside of a transaction, we will create a Node object by calling another method to find our user and then return all the properties of the user.

@Path("/users")
public class Users {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @GET
    @Path("/{username}")
    public Response getUser(@PathParam("username") final String username, @Context GraphDatabaseService db) throws IOException {
        Map<String, Object> results;
        try (Transaction tx = db.beginTx()) {
            Node user = findUser(username, db);
            results = user.getAllProperties();
            tx.success();
        }
        return Response.ok().entity(objectMapper.writeValueAsString(results)).build();
    }


Our findUser method is going to be used a ton I imagine so that’s why we are breaking it out. If it finds the user in the database it returns that user otherwise it errors out which means our endpoint will return an HTTP 400 error. Notice we make sure to use the lowercase version of the username.

public static Node findUser(String username, @Context GraphDatabaseService db) {
    if (username == null) { return null; }
    Node user = db.findNode(Labels.User, USERNAME, username.toLowerCase());
    if (user == null) { throw UserExceptions.userNotFound; }
    return user;
}


We want to be good developers so we will write a little test for our method as well. I love the way Neo4j lets you create an in memory throw away instance for quick and easy testing. We will use the Neo4jRule class to create our temporary instance with a node that already exits as our Fixture and reference our extension Users class:

public class GetUserTest {
    @Rule
    public Neo4jRule neo4j = new Neo4jRule()
            .withFixture(FIXTURE)
            .withExtension("/v1", Users.class);

    private static final String FIXTURE =
            "CREATE (max:User {username:'maxdemarzi', " +
                    "email: 'maxdemarzi@hotmail.com', " +
                    "name: 'Max De Marzi'," +
                    "password: 'swordfish'})";

    private static final HashMap expected = new HashMap<String, Object>() {{
        put("username", "maxdemarzi");
        put("email", "maxdemarzi@hotmail.com");
        put("name", "Max De Marzi");
        put("password", "swordfish");
    }};


The test itself creates the schema and requests the user, asserting that we get the result we were expecting.

@Test
public void shouldGetUser() {
    HTTP.POST(neo4j.httpURI().resolve("/v1/schema/create").toString());

    HTTP.Response response = HTTP.GET(neo4j.httpURI().resolve("/v1/users/maxdemarzi").toString());
    HashMap actual  = response.content();
    Assert.assertEquals(expected, actual);
}


Now we need an endpoint to create a user. We’re going to make sure we don’t create a user with the same username or email as an existing user, so we’ll check those and if we’re good we’ll set the node properties. We’re getting a little ahead of ourselves here because we are expecting a bunch of properties we haven’t talked about yet. But these will be required for the dating site to work. For example we need to know what someone is looking for and well as what they are gender wise. We also want to know where they live and how far they are willing to look for love, so we can match them with people in their dating area. We may need to tweak this later, but it’s a good start.

@POST
public Response createUser(String body, @Context GraphDatabaseService db) throws IOException {
    HashMap parameters = UserValidator.validate(body);
    Map<String, Object> results;
    String username = ((String)parameters.get(USERNAME)).toLowerCase();
    String email = ((String)parameters.get(EMAIL)).toLowerCase();
    try (Transaction tx = db.beginTx()) {
        Node user = db.findNode(Labels.User, USERNAME, username);
        if (user == null) {
            user = db.findNode(Labels.User, EMAIL, email);
            if (user == null) {
                user = db.createNode(Labels.User);
                user.setProperty(EMAIL, email);
                user.setProperty(NAME, parameters.get(NAME));
                user.setProperty(BIO, parameters.get(BIO));
                user.setProperty(USERNAME, username);
                user.setProperty(PASSWORD, parameters.get(PASSWORD));
                user.setProperty(IS, parameters.get(IS));
                user.setProperty(IS_LOOKING_FOR, parameters.get(IS_LOOKING_FOR));
                user.setProperty(HASH, new Md5Hash(email).toString());
                user.setProperty(TIME, ZonedDateTime.now(utc));
                user.setProperty(DISTANCE, parameters.get(DISTANCE));

                Node city = db.findNode(Labels.City, FULL_NAME, parameters.get(CITY));
                user.createRelationshipTo(city, RelationshipTypes.IN_LOCATION);

                results = user.getAllProperties();
            } else {
                throw UserExceptions.existingEmailParameter;
            }
        } else {
            throw UserExceptions.existingUsernameParameter;
        }
        tx.success();
    }
    return Response.ok().entity(objectMapper.writeValueAsString(results)).build();
}


You may have noticed I used a UserValidator class to check the input. This class basically does this to all the input parameters and errors out if we run into invalid data:

if (!input.containsKey(USERNAME)) {
    throw UserExceptions.missingUsernameParameter;
} else {
    String username = (String)input.get(USERNAME);
    if (username.equals("")) {
        throw UserExceptions.emptyUsernameParameter;
    } else if (!username.matches(usernamePattern)) {
        throw UserExceptions.invalidUsernameParameter;
    }
}


I’ll spare you the test code for createUser but it’s in the repository if you want to see it.

At this point we have a schema and we can create and retrieve users, our dating site is not super useful yet, but stay tuned and we will add more functionality in the next part.

Neo4j Database

Published at DZone with permission of Max De Marzi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Top 10 Secure Coding Practices Every Developer Should Know
  • ChatGPT Prompts for Agile Practitioners
  • Differences Between Site Reliability Engineer vs. Software Engineer vs. Cloud Engineer vs. DevOps Engineer
  • Top 5 Node.js REST API Frameworks

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: