Launching Baby Registry on Walmart.com

Walmart Labs launched a new baby registry experience at the beginning of April. I was part of the engineering team that rewrote the web application and services. Walmart doesn’t ship new customer-facing products frequently; this presents an interesting case study into how a large corporation creates something new.

Engineering efforts began in late 2018: a new team of mobile, service and web folks transitioned from well-staffed legacy applications or joined specifically for the project. I found myself looking for web work after deciding to leave the core mobile team (more on that later), and registry sounded like a fun return to my old JavaScript stack.

Services

The MVP’s customer entrypoint centered around an interactive chat that translated users’ preferences to a fully stocked registry. This also (hopefully) makes registry creation a bit more fun for users. Services borrowed an engineer for the initial work, a generic chat service with canned questions using GraphQL and Apollo Server via hapi. Our team quickly found the codebase inflexible as requirements shifted. Chat composed a small portion of the experience: we also needed several queries and mutations to power the clients’ views.

Schema-Driven

One of our fantastic iOS engineers introduced us to the concept of “backend-for-frontend,” where a single service collates responses from multiple APIs. Clients only request data from backend-for-frontend, improving network overhead and caching. Fortunately, iOS, Android and web maintain similar user interfaces, so all clients could rely on the same queries and mutations to power their experiences. We crafted a schema that sensibly mapped services’ responses for the clients.

Separately, I decided that our original chat design didn’t meet our needs. A more flexible workflow presented itself:

+----------+
| Question |---> Answer
+----------+       |
     ^             |
     |             |
     +-------------+

The previous answers are passed into the next question, which is a function that returns an answer. This allows for question branching and the possibility of a more dynamic series of responses.

TypeScript

I wanted to explore TypeScript due to its recent praise. TypeScript was new to many folks on the team, including me. After a week of learning the basics, I found myself more productive writing TypeScript compared to JavaScript: refactors were a breeze (especially with VS Code’s language integration), unit tests required less assertions due to type safety, and adding features felt natural.

Web Application

The web team started development in November 2018 using React, redux, and Walmart’s homegrown framework, electrode. We lost our senior engineer to another team early on, so I assisted with code reviews while working on services. After the services team gained another staff engineer, I transitioned over to web full time.

More TypeScript

Buoyed by the benefits of TypeScript on services, we migrated our web codebase to TypeScript. The front-end has more complexity due to the large DOM and React API surface areas. We finally fully migrated to TypeScript in January – not without a handful of any safety escapes. This unlocked massive development speedup over the following weeks.

We opted for a minimal GraphQL client to avoid the overhead of Apollo’s client. The service relied on gql2ts for build-time response alignment with the schema. This assisted our web client development as we were able to use the same schema-derived types. For example:

type Registry {
  metadata: RegistryMetadata!
  items: [RegistryItem!]!
}
type RegistryMetadata {
  date: String!
  gender: RegistryGender!
  title: String!
  # ...
}
type RegistryItem {
  id: ID!
  name: String!
  price: Float!
  quantity: Int
  # ...
}

This translates to these TypeScript interfaces:

export interface IRegistry {
  metadata: IRegistryMetadata
  items: IRegistryItem[]
}
export interface IRegistryMetadata {
  date: string
  gender: RegistryGender
  title: string
  // ...
}
export interface IRegistryItem {
  id: string
  name: string
  price: number
  quantity: number | null
  // ...
}

The web client only requires fields necessary for its UI. Using RegistryItem as an example, we only need name and price, so we only request those fields:

import { IRegistry, IRegistryItem } from 'my-types'

export type RegistryFields = Omit<IRegistry, 'items'> & {
  items: Pick<IRegistryItem, 'name' | 'price'>
}

export const registryFields = `{
  metadata: {
    date
    gender
    title
    # ...
  }
  items {
    name
    price
  }
}`

The RegistryField type is paired with the registryFields schema over a genericized fetch-based wrapper, and RegistryField is used in a redux reducer’s state. The alignment of types-to-request is a bit fragile: changes require modifying both RegistryFields and registryFields. It does, however, presents huge benefits: the client now aligns correctly with the API, and changes result in a breaking build instead of runtime errors.

A Web of Problems

Product and design focused on mobile applications, leaving web as an afterthought. Our design department had a high turnover rate: at least three different designers owned visuals throughout the project, leaving us with incomplete mockups and no UX specification. While we had a wealth of web development expertise, most were new to the many challenges of web development at Walmart Labs. The result? Engineer was behind big time as a strict launch deadline approached.

My lovely engineering manager’s continued efforts to warn leadership of the problems eventually paid off when we received reinforcements, including an old MRN cohort, Chris. We re-prioritized the backlog for a bare bones MVP, and began hacking.

At the end of Februrary we lacked functional pages for any part of the experience. Chris and I worked evenings and met for weekend coffees and code. Through the web engineering team’s tireless efforts we delivered: everyone made personal sacrifices (several ~60 hour weeks on my part) to ship this thing. Despite discovering new requirements, changing features’ designs, encountering serious ADA compliance issues, and finding a handful of major bugs, we were ready to ship by the deadline.

When we finally ramped up to 100% it was a relief. Everything worked! The redesign has already increased new baby registry creation significantly, and we received some great press.

Project Recap

What did we accomplish? What did we learn?

  • Sweating details at the expense of a foundation guarantees problems: Initial efforts focused on the entrypoint to the experience when we should have built up base functionality and integrations. This hurt us towards the end as we encountered several missing pieces.
  • Waterfall doesn’t work: Departments functioned in isolation instead of working as a team. We realized this post-launch during retro meetings with product, research, design and engineering. Waterfall inhibits design from ensuring their solutions work and prevents researchers from validating the product addresses customers’ needs, ultimately burdening engineering to hit inflexible deadlines. This promotes burnout, which we don’t want.
  • TypeScript was essential: We wouldn’t have shipped on time without TypeScript’s safety, which protected us from bugs throughout development. In fact, it saved us so much time that we shipped a major feature originally cut from the MVP’s scope. We also had the benefit of strongly typed GraphQL schema coupled with TypeScript definitions: wiring up the client to the API was seamless. “I’ve never seen anything like it. It just worked!” exclaimed Chris.
  • Firsts for Walmart.com: As far as I know, we shipped the first customer-facing web application written in TypeScript. It was one of the first to be powered by a GraphQL service.

What Next?

The web team must repay some post-MVP technical debt (increasing test coverage, improving analytics). We’ll listen to our users’ feedback through data collection and research to hone the product into something that increasingly helps customers save money and live better.

Aside from user-facing features, we discovered some thorny areas within Walmart Labs’ developer ecosystem, including underwhelming service documentation and subpar developer tooling. I personally find this energizing: we can adopt aspects of startups (agility, use of existing open source solutions) and improve our tooling and frameworks, which will result in happier engineers, healther inter-sourcing, faster feature delivery and bug fixes, and ultimately better user experiences. We can also impart our technology findings to the others: good initial technology choices (TypeScript, GraphQL) made us successful, and I’d like to encourage their use throughout the greater organization.

Sound exciting? Good news: we’re hiring in Portland, Oregon. Check out our job listings on careers.walmart.com or shoot me an email for more details.