Building A Customer Journey using Domain Driven Design and GraphQL

Update: On April 16th 2021, Slash GraphQL was officially renamed Dgraph Cloud. All other information below still applies.

In this blog, you will learn how you can design an ordering journey using GraphQL and align with some concepts from the Domain Driven Design technique. You will also learn how GraphQL easily accommodates changes in the ordering journey.

You can opt to execute all the steps mentioned here on the Slash GraphQL backend (Dgraph hosted GraphQL) or your own Dgraph instance. The Slash GraphQL backend (or just “Slash”) is used often in this blog.

Slash GraphQL provides an easy to use interface for authoring the schema as well as running queries and mutations. You can begin with the steps mentioned in the Slash GraphQL Quickstart.

Digital Customer Journeys

The Ordering Journey

Customers expect to have nuanced journeys in their interaction with several aspects of sales, including ordering, shipping and payments. For example, a customer may want to order using a voice channel, send a pinned location on a map as delivery location and rely on self service for returns and payments.These ordering journeys are characterized by a reliance on a mesh of API-driven apps. With Dgraph, you get out-of-the-box support for GraphQL APIs.

Another unique aspect of these journeys is the iterative style of development involved. Developers rely directly on feedback from active users, and constantly update the coding artifacts involved. A major impediment to rapid iterations is the time taken by developers to implement changes made to the data model. Developers tend to make changes to the database, and then refactor the API to accommodate the changes across the Create, Read, Update and Delete (CRUD) actions. In this blog, you will learn modeling techniques using GraphQL and Dgraph that support these rapid iteration needs.

Understanding the Ordering Journey

Understanding the Ordering Journey

Let’s imagine an ordering journey that involves the steps shown above. The ordering journey involves submitting an order, an acknowledgement, shipment and finally invoicing. This journey is expected to evolve iteratively, with changes expected to processing steps as well as the data model involved. The changes may include popular features such as self-service return, and fluent interactions through social media such as login and payments.

The Order and associated process is related to Customer and Product entities. These entities together constitute the domain of the Ordering Journey.

Modeling the Journey Domain

Modeling the Ordering Journey

You will use a Domain-Driven Design (DDD) based approach to design the ordering journey. DDD aims to simplify software development by tying the business domain and the development of software assets in a relatively tight loop. This style of design is relevant in organizations where multiple teams collaborate to deliver solutions, often in the backdrop of rapidly evolving requirements.

In this blog, you will focus on the “bounded context” and “ubiquitous vocabulary” aspects of DDD. The “bounded context” is a collection of entities and processes critical to your domain and your team. “Ubiquitous Vocabulary” deals with enforcing consistent interpretation of data and processes across participating teams, including backend teams, frontend teams and business analysts.

In your ordering journey, the moving parts are the order process and related entities form the bounded context. You will use a GraphQL schema to express your process and data. Dgraph creates consistent CRUD APIs based on this GraphQL schema. In turn, these APIs enforce the ubiquitous vocabulary in the ordering journey across participating teams. This same GraphQL schema is leveraged heavily when we need to accomodate changes.

This blog doesn’t explore dependencies or the “context map” in-depth. Future blog posts will focus on context maps and other concepts from domain-driven design (like aggregates), along with microservices development using Dgraph. The ordering journey and associated entities introduced in this blog post will provide the necessary context. Watch this space for more!

Creating the GraphQL Schema

Parts of Ordering Journey

The “OrderProcess” and the “OrderJourney” are important parts of the model. The “OrderProcess” holds the lifecycle of one ordering journey, while the “OrderJourney” is used to model parts of this lifecycle. The “OrderJourney” is modeled as a union of discrete process steps conforming to a common interface “OrderJourneyElement”. These steps include Order submit, acknowledgement, shipping and invoicing. This technique allows space for future extensions or modifications in the ordering journey and helps to absorb changes in the ordering journey at a faster rate.

Order Journey Schema

The schema for this “OrderProcess” and “OrderJourney” is as shown above.

Order related schema The Order itself consists of the items and customer involved as well as a link to the ordering process.

The complete schema is shown below. You can set this schema directly in a Slash GraphQL backend as mentioned here.

type SubmitOrder implements OrderJourneyElement {
  updatedOn: DateTime @search
  submittedBy: Customer!
}

type AcknowledgeOrder implements OrderJourneyElement {
  acknowledgedOn: DateTime @search
}

type ShipOrder implements OrderJourneyElement {
  shippingAddress: String! @search(by: [term])
}

type InvoiceOrder implements OrderJourneyElement {
  billingAddress: String! @search(by: [term])
  preferredEmailAddress: String @search(by: [hash])
  billingBlock: Boolean @search
}

union OrderJourney = SubmitOrder | AcknowledgeOrder | ShipOrder | InvoiceOrder

interface OrderJourneyElement {
    id: ID!
  version: String
}

type OrderProcess {
  id: ID!
  order: Order! @hasInverse(field: relatedProcess)
  processStep: [OrderJourney]
}

type Order {
  id: ID!
    relatedProcess: OrderProcess
  orderDate: DateTime @search
  fromChannel: Channel
  items: [Item!]!
}

enum Channel {
  Online
  Store
}

type Customer {
  id: ID!
  customerName: String! @search(by: [exact,regexp])
  customerAddress: String @search(by: [term])
}

type Item {
  id: ID!
  product: Product!
  quantity: Int @search
}

type Product {
  id: ID!
  productName: String! @search(by: [term])
  productDescription: String @search(by: [term])
}

The Ordering Journey in Action

Add an Order Process

The GraphQL API is generated from this schema and provides clearly defined processing steps in the “Ubiquitous vocabulary” or local lingo of the ordering journey. Given that Dgraph will directly store data based on this schema, there is no room for confusion across API and storage layers.

Let’s check the addOrderProcess GraphQL mutation. It shows that this API can accept an order as well as details about the processing involved. Please also note that the client only sees the processing options in the API as were defined in the ordering journey context. For example, the OrderProcess API only allows the four processing steps i.e., submit, acknowledge, shipment and invoice. This removes ambiguity and promotes a common understanding across backend teams, frontend teams and business analysts involved. Thus, the “Ubiquitous Vocabulary” is consistently enforced.

Finally, by explicitly configuring your understanding of the ordering journey, you are better prepared for changes. It is very clear where changes need to happen. For example, if you want to add a ReturnOrder processing step, you can follow the same design flow make the necessary changes.

Step by step

You can execute the following mutations directly using the API explorer using Slash. Please refer to the documentation here.

mutation CreateReferenceData {
  addCustomer(input: {
    customerName: "John Doe"
  }) {
    customer {
      id
    }
  }
  addProduct(input: 
      {
        productName: "Socks", 
        productDescription: "Grey Socks"
      }) 
  {
    product {
      id
    }
  }
}

You will first add a product and a customer and note the id of the product and customer created. Let’s assume that the product is created with id 0x7534 and customer has id 0x7533.

mutation AddOrderProcess {
  addOrderProcess(input: 
    {
      order: {
        fromChannel: Online, items: {
          quantity: 10, product: {id: "0x7534"}}
        }, 
       processStep: {
        submitOrderRef: {
          submittedBy: {id: "0x7533"}}
        }
      }) {
    orderProcess {
      id
    }
  }
}

In the next step, you will add a new order process and start off the journey with a Submit step. The id of the order process just created is noted as well. Let’s assume the order process has an id 0x753e.

You are now ready to process the other steps involved in the journey.

Completing the Ordering Journey

mutation AcknowledgeOrder {
  updateOrderProcess(input: {
    filter: {
      id: ["0x753e"]
    }, set: {
      processStep: {
        acknowledgeOrderRef: {
          acknowledgedOn: "2020-12-15"}
        }
      }
    }) {
    numUids
  }
}
mutation ShipOrder {
  updateOrderProcess(input: {
    filter: {
      id: ["0x753e"]
    }, set: {
      processStep: {
        shipOrderRef: {shippingAddress: "twenty-two jump street"}
      }
    }
  }) {
    numUids
  }
}
mutation InvoiceOrder {
  updateOrderProcess(input: {
    filter: {
      id: ["0x753e"]
    }, set: {
      processStep: {
        invoiceOrderRef: {billingAddress: "One Main Street"}
      }
    }
  }) {
    numUids
  }
}

Let’s continue the journey by completing the acknowledgement, shipping and invoicing steps. Acknowledgement involves setting the date of acknowledgement, shipping involves confirming the shipping address while invoicing involves the billing related attributes like billing address.

Reporting the Ordering Journey

query report {
  getOrderProcess(id: "0x753e") {
    id
    processStep {
      ... on SubmitOrder {
        id
        submittedBy {
          customerName
        }
      }
      ... on AcknowledgeOrder {
        acknowledgedOn
      }
      ... on ShipOrder {
        shippingAddress
      }
      ... on InvoiceOrder {
        billingAddress
      }
    }
    order {
      fromChannel
      items {
        product {
          productName
          productDescription
        }
        quantity
      }
    }
  }
}
{
  "data": {
    "getOrderProcess": {
      "id": "0x753e",
      "processStep": [
        {
          "id": "0x753d",
          "submittedBy": {
            "customerName": "John Doe"
          }
        },
        {
          "acknowledgedOn": "2020-12-15T00:00:00Z"
        },
        {
          "shippingAddress": "twenty-two jump street"
        },
        {
          "billingAddress": "One Main Street"
        }
      ],
      "order": {
        "fromChannel": "Online",
        "items": [
          {
            "product": {
              "productName": "Socks",
              "productDescription": "Grey Socks"
            },
            "quantity": 10
          }
        ]
      }
    }
  }
}

Finally, you can the report the process’ progression by using the query attached. The query encourages the use attribute specification in context with the actual usage involved. For example, you can specify the shipping address under the ShipOrder step. This reduces ambiguity and provides clarity to the client about how a particular attribute has been used.

Dealing with Changes

Adding a delivery location

type ShipOrder implements OrderJourneyElement {
    shippingAddress: String! @search(by: [term])
    deliveryLocation: Point
}

The customer may wish to pass the delivery location as a pin on a map. From an API perspective, this will translate to a point that can be specified with a latitude and longitude. In order to meet this requirement, you can extend the shipping step in our process and add a delivery location as shown. Dgraph supports Point data type in its GraphQL API. This can be deployed directly into Dgraph. In the following example, let us assume the ShipOrder id is 0x7540.

mutation ShipOrderWithCoordinates {
  updateShipOrder(input: {
    filter: {
      id: ["0x7540"]
    }, set: {
      deliveryLocation: {
        longitude: -122.4214895, latitude: 37.7896108}
      }
    }) {
    shipOrder {
      id
    }
  }
}

You can then pass latitude and longitude via the GraphQL API. Next, query this information and use the results for visualization as well as any geo-specific queries (near, contains, within etc.). By directly supporting the change in the right context of the overall customer journey (in this case geo location in the context of shipping), and providing type specific storage and queries via the GraphQL API, Dgraph allows you to rapidly and correctly implement changes in your application. Map View

“Ratel”, a graph visualization app provided by Dgraph, helps visualize the delivery coordinates on a map. You can access your Ratel page on Slash by clicking the Run DQL Query button on the API Explorer page on Slash. Querying the data via Ratel shows the view above.

Adding a process step

Let’s say that the customer is demanding a modification in the ordering process. The new requirement is to provision an order return step where the pickup location can be provided by a point on the map. We can model this by adding a ReturnOrder step as below.

type ReturnOrder implements OrderJourneyElement {
  reason: String! @search(by: [term])
    pickupLocation: Point
}

union OrderJourney = SubmitOrder | AcknowledgeOrder | ShipOrder | InvoiceOrder | ReturnOrder

The refreshed API signature will look as below. Return Orded Journey

You have successfully added a step in the ordering journey by making a limited, focused change in the schema. Dgraph ensures that this change is reflected across storage, and APIs.

Conclusion

In this blog, you explored the capabilities of Dgraph and GraphQL in the context of an ordering journey. You utilized concepts from domain driven design using GraphQL to bring a strong focus on your business process and inter-communication between participating teams. Finally, you adapted your solution when changes were needed by making limited and focused updates to the schema. You demonstrated the CRUD APIs generated by Dgraph automatically manage storage aspects, and helps in maintaining consistent and iterative development style across participating teams.

Interested in getting started with Dgraph? You can get started here. Please say hello to our community here; we will be glad to help you in your journey!