Config Based Data Layer
October 11, 2021
Rationale
This is a post about a little pattern I stumbled upon while doing some refactoring, a pattern that’s changed how I write React code.
Much of what we do as web-app developers is repetitive. Get some data and cache it, show some html based on some data, handle click events, repeat. We use common tools, like fetch
, Redux, and React. It can get really repetitive, especially if you use common Redux and React patterns.
A typical React+Redux app will contain a bunch of files that define your actions, thunks, reducers, selectors, store, state, requests, responses, models, components, containers, views, routes, effects, epics, sagas, observables, subscriptions, etc… Not to mention all the unit tests, integration tests, and end to end tests that go along to verify that the functionality of all those bits of code remains consistent. All of those words are entire concepts unto themselves and have a history of years worth of threads, flame wars, tutorials, articles, and talks about them. For a beginner, it can feel daunting to even approach the subject, much less write and maintain all the code to implement those concepts.
In practice, building React components can often be quite boring. Components start simply: const Foo = () => {return <div>hello world</div>;}
; it’s too trite to even make a snippet of. Bring in useEffect
and useState
to change the page, and draw the rest of the owl.
Conversely, there seems to be myriad ways to construct a data layer. In my experience, the data layer of every app I’ve worked on has been different each time. No one seems to agree. So, here I am, proposing yet another way to make a data layer for your web app.
Conventionally, you’d start an app with useEffect and useState. Then, when things got more complicated than a handful of components, you’d add some state management. Maybe you’ll use vanilla React Context or Unstated. Maybe you like to be complicated and choose vanilla Redux. You’ll soon notice that not everything you do is synchronous, so you might add Redux Thunk, Redux Saga, or even (for the very brave) Redux Observable. Your code can start to balloon with terms and phrases you’ve never seen before, making you feel like you can’t even understand your own code, much less explain it to someone else. Add to all of that, your app is so slow. React seems so complicated!
Configuration over Convention
I think the word Configuration has gotten a bad wrap. The term “Convention over Configuration” got to be really popular about 15 years ago and is still du-jour for open source libraries. Yet, configuration always creeps in. Your root folder eventually becomes littered with configuration files, regardless of how hard you try to stay in your lane. I say embrace it. Let’s make our entire apps configuration instead of being conventionally built with code!
So, what is a config based data layer, and how do I make it?
Let’s start with what I mean by “the data layer”, using an onion as a metaphor for the architecture of an app. Onions have layers with distinct boundaries. A common pattern is to keep API code, or data access code, together. Above that, you may have some services, controllers, views, and components that all combine to make your app. Keeping your code organized using layers helps to drive out circular dependencies and allows “seams” to emerge.
[Show Chalkboard Diagram Here]
The data layer is where all your API access code lives. It is the class, module, folder, or set of files united by a common naming convention that abstracts away how you access the external world. It is how you get data into and out of your application. It can have many different names, and can be implemented using many different patterns, but it’s job is really simple. Call a method, optionally providing some JSON, and get some data in return.
Bootstrapping a TODO MVC app
We will create a TODO MVC clone to demonstrate the pattern in action. We will use “Create React App” to bootstrap the application and the canonical TODO MVC CSS to style our app. Finally, because this is a story about the data layer, we will implement a GET using an open API: a list of public apis, as the starting point for our list of TODOs.
yarn create react-app my-app --template typescript
yarn add todomvc-app-css
yarn add react-redux
yarn add react-router
yarn add redux-observable
yarn add @reduxjs/toolkit
The Good Bits
Now, on to the good bits.