Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux/Java: Managing normlized data & multiple model representations per entity

We are building a new app using React/Redux which is rendered server side.

We wish to follow best practice for Redux and normalize our data on the server before it's passed into the initial state for the store.

For this example, let's say we have a generic 'Products' entity that can be quite complex and is normalized on the root of our store and page level state in another object on the root of the store. So the structure and Reducers follow the typical 'slice reducer' pattern and will look like this:

{
  page_x_state: PageReducer
  products: ProductsReducer
}

We are using combine reducers to merge the reducers before passing them into the store.

Theoretical use case: We have a 'products' page that shows a list of basic product info. A user can click on a product to show a modal which then loads and shows the complete product data.

For the above example, the state sent from the server will contain only basic product models (3 or 4 fields), this is enough to render the table and fetching all product information at this point is wasteful and not very performant.

When a user clicks a product we will do an AJAX call fetch all data for that product. Once we have all data for the single product, should we update the instance in the products store with a full model? If so, we would then end up with a set of objects all of which could be different states (some could have minimal fields vs some which are full-blown objects with 10s of fields). Is this the best way to handle it?

Also, I would be interested to hear any thoughts of managing different representations of the same underlying model on the server and how to map it to the Redux store (in Java ideally).

like image 737
eddnichols Avatar asked Nov 17 '17 17:11

eddnichols


People also ask

What is normalized state Redux?

A normalized state is a way to store (organize) data. With this way each entity type will have its own place in the store, so that there is only a single point of truth. This practice is the recommended way to organize data in a Redux application as you can read in the Redux recipes.

Does Redux improve performance?

In fact, React Redux in particular is heavily optimized to cut down on unnecessary re-renders, and React-Redux v5 shows noticeable improvements over earlier versions. Redux may not be as efficient out of the box when compared to other libraries.

What are 3 main concepts of Redux?

In this brief introduction to Redux, we'll go over the main concepts: reducers , actions , action creators and store .

Can I use Redux as a database?

This library helps you to organize your state in a relational way, with queries and joins as you would expect from an SQL based database. There is a storage adaper for redux or you can use it as a standalone library (redux-database has no dependencies!).


4 Answers

EDIT:

Explicitly answering your first question, if your reducers are built up correctly your whole state tree should initialize with absolutely no data in it. But should be the correct shape. Your reducers should always have a default return value - when rendering server side - Redux should only render the initial state

After server-side rendering, when the store (that is now client side) needs updating because of a user action, your state shape for all of your product data is already there (it's just that some of it will probably be default values.). Rather than overwriting an object, your just filling in the blanks so to speak.

Lets say, in your second level view you need name, photo_url, price and brand and the initial view has 4 products on it, your rendered store would look something like this:

{
  products: {
    by_id: {
      "1": {
         id: "1",
         name: "Cool Product",
         tags: [],
         brand: "Nike",
         price: 1.99,
         photo_url: "http://url.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       },
       "2": {
         id: "2",
         name: "Another Cool Product",
         tags: [],
         brand: "Adidas",
         price: 3.99,
         photo_url: "http://url2.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       },
       "3": {
         id: "3",
         name: "Crappy Product",
         tags: [],
         brand: "Badidas",
         price: 0.99,
         photo_url: "http://urlbad.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       },
       "4": {
         id: "4",
         name: "Expensive product",
         tags: [],
         brand: "Rolex",
         price: 199.99,
         photo_url: "http://url4.com",
         category: "",
         product_state: 0,
         is_fetching: 0,
         etc: ""
       }
     },
    all_ids: ["1", "2", "3", "4"]
  }
}

You can see in the above data some keys are just empty strings or an empty array. But we have our data we need for the actual initial rendering of the page.

We could then make asynchronous calls on the client in the background immediately after the server has rendered and the document is ready, the chances are the server will return those initial calls before the user tries to get the data anyway. We can then load subsequent products on user request. I don't think that's the best approach but it's the one that makes most sense to me. Some other people might have some other ideas. It entirely depends on your app and use-case.

I would only keep one products object in state though and keep ALL the data pertaining to products in there.


I recently deployed an app into production and i'll share some of my insights. The app, whilst not being too large in size, had a complex data structure and having gone through the whole process as a newbie to Redux in production (and having guidance from my architect) – These are some of our takeaways. There's no right way in terms of architecture but there certainly are some things to avoid or do.

1. Before firing into writing your reducers design a 'static' state

If you don't know where you are going, you can't get there. Writing the whole structure of your state out flat will help you reason about how your state will change over time. We found this saved us time because we didn't have to really rewrite large sections.

2. Designing you state

keep it simple. The whole point of Redux is to simplify state management. We used a lot of the tips from the egghead.io tutorials on Redux that were created by Dan Abramov. They are clear really helped solve a lot of issues we were encountering. i'm sure you've read the docs about normalising state but the simple examples they gave actually carried through in most data patterns we implemented.

Rather than creating complex webs of data each chunk of data only held it's own data if it needed to reference another piece of it data it only referenced it by id we found this simple pattern covered most of our needs.

{
  products: {
    by_id: {
     "1": {
        id: "1",
        name: "Cool Product",
        tags: ["tag1", "tag2"],
        product_state: 0,
        is_fetching: 0,
        etc: "etc"
      }
    },
    all_ids: ["1"]
  }
}

In the example above, tags might be another chunk of data with a similiar data structure using by_id and all_ids. All over the docs and tut, Abramov keeps referencing relational data and relational databases this was actually key for us. At first we kept looking at the UI and designing our state around how we thought we were going to show it. When this clicked and we started grouping the data based on it's relationship to other pieces of data, things started to click into place.


Quickly flipping to your question, I would avoid duplicating any data, as mentioned in another comment, personally i'd simply create a key in the state object called product_modal. let the modal take care of it's own state...

{
  products: {
    ...
  },
  product_modal: {
    current_product_id: "1",
    is_fetching: true,
    is_open: true
  }
}

We found following this pattern with page state worked really well as well...we just treated it like any other piece of data with an id/name etc.


3. Reducer Logic

make sure reducers keep track of their own state. a lot of our reducers looked quite similiar, at first this felt like DRY hell but then we quickly realised the power of more reducers...say an action is dispatched and you want to update a whole chunk of state..no probs just check in your reducer for the action and return the new state. If you only want to update one or two fields in the same state...then you just do the same thing but only in the fields you want changing. most of our reducers were just simply a switch statement with an occasional nested if statement.

Combining Reducers

We didnt use combineReducers, we wrote our own. It wasn't hard, it helped us understand what was going on in Redux, and it allowed us to get a little smarter with our state. This tut was invaluable

Actions

Middleware is your friend...we used fetch API with redux-thunk to make RESTful requests. We split the required data requests into separate actions which called store.dispatch() for each data chunk that needed updating for the call. Each dispatch dispatched another action to update state. This kept our state updated modularly and allowed us to update large sections, or granularly as needed.

Dealing with an API

Ok so there's way too much to deal with here. I'm not saying our way is the best...but it has worked for us. Cut short...we have an internal API in java with publically exposed endpoints. The calls from this API didn't always map to the front end easily. We haven't implemented this, but ideally, an initial init endpoint could have been written on their end to get a lump of initial data that was needed to get things rolling on the front end for speeds sake.

We created a public API on the same server as the app, written in PHP. This API abstracted the internal API's endpoints (and in some cases the data too) away from the front end and the browser.

When the app would make a GET request to /api/projects/all the PHP API would then call our internal API, get the necessary data (sometimes across a couple of requests) and return that data in a usable format that redux could consume.

This might not be the ideal approach for a javascript app but we didn't have the option to create a new internal API structure, we needed to use one that has existed for several years, we have found the performance acceptable.

like image 50
Matthew Brent Avatar answered Oct 21 '22 09:10

Matthew Brent


should we update the instance in the products store with a full model

It should be noted that Java and ReactJs+Redux don't have much conceptual overlap. Everything is a Javascript Object, not an Object with a Class.

Generally, storing all the data you receive in the Redux store state is the way to go. To work around the fact that some of the data will be minimal and some will be fully loaded you should make a conditional ajax call in the onComponentWillMount method of the individual product display container.

class MyGreatProduct extends React.Component {
  onComponentWillMount() {
    if(!this.props.thisProduct.prototype.hasProperty( 'somethingOnlyPresentInFullData' )) {
      doAjaxCall(this.props.thisProduct.id).then((result) => {
        this.props.storeNewResult(result.data);
      }).catch(error=>{ ... })
    }
  }

  // the rest of the component container code
}

const mapStateToProps = (state, ownProps) => {
  return {
    thisProduct: state.products.productInfo[ownProps.selectedId] || {id: ownProps.selectedId}
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    storeNewResult: (data) => { dispatch(productDataActions.fullProductData(data)) }
}

export default connect(mapStateToProps, mapDispatchToProps)(MyGreatProduct);

With this code, it should be somewhat clear how agnostic the components and containers can be regarding the exact data available in the Store at any given time.


Edit: In terms of managing different representations of the same underlying model on the server and how to map it to the Redux store, I'd try to use the same relative looseness you are dealing with once you have JSON. This should eliminate some coupling.

What I mean by this is just add the data you have to a JSObject to be consumed by React + Redux, without worrying too much about what values could potentially be stored in the Redux state during the execution of the application.

like image 36
Shammoo Avatar answered Oct 21 '22 07:10

Shammoo


There's probably no right answer, just which strategy you prefer:

  1. The simplest strategy is to add another piece to your reducer called selectedProduct and always overwrite it with the full object of the currently selected product. Your modal would always display the details of the selectedProduct. The downfalls of this strategy are that you aren't caching data in the case when a user selects the same product a second time, and your minimal fields aren't normalized.

  2. Or you could update the instance in your Products store like you said, you'll just need logic to handle it. When you select a product, if it's fully loaded, render it. If not, make the ajax call, and show a spinner until its fully loaded.

like image 26
Jeff F. Avatar answered Oct 21 '22 09:10

Jeff F.


If you don't have a concern with storing extra that data in the redux store it's not actually going to hit your performance very much if you use a normalized state. So on that front I would recommend caching as much as you can without risking security.

I think the best solution for you would be to use some redux middleware so your front end doesn't care how it gets the data. It will dispatch an action to the redux store and the middleware can determine whether or not it needs an AJAX call to get the new data. If it does need to fetch the data then the middleware can update the state when the AJAX resolves, if it doesn't then it can just discard the action because you already have the data. This way you can isolate the issue of having two different representations of the data to the middleware and implement a resolution there for it so your front end just asks for data and doesn't care how it gets it.

I don't know all the implementation details so as Jeff said its probably more what you prefer but I would definitely recommend adding some middleware to handle your AJAX calls if you haven't already it should make interfacing with the store much simpler.

If you want to read more on middleware the Redux documentation is pretty good.

https://redux.js.org/docs/advanced/Middleware.html

like image 32
Dakota Avatar answered Oct 21 '22 08:10

Dakota