New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Where to put business logic / validations? #1165

Closed
arunoda opened this Issue Dec 21, 2015 · 11 comments

Comments

@arunoda

arunoda commented Dec 21, 2015

I'm right now learning Redux. So, I'm looking for some places where we can put business logic and validations.

The App

It's a simple counter. And the counter value must be sit between 1-5. I has no idea on where to put my app's business logic. So, this is what I did.

Inside Reducers

When there is no server side async stuff, I put my validations inside the reducer like this.
See: https://github.com/arunoda/learn-redux/blob/dir-structure/src/reducers/index.js#L6
It worked pretty well.

Inside Action Creators

When I need to sync the store with the server, I used redux-thunk. Now I had to validate in the action creators as well. So, instead of validating in the reducers, I moved validation logic into the action creators. See: https://goo.gl/VC8lmp

So, I think it's best to put validation/business logic into action creators, specially with redux-thunk. (where I can get the store state easily). Is this the pattern used normally, or is there any better approaches?

@gaearon gaearon added the question label Dec 21, 2015

@davezuko

This comment has been minimized.

Show comment
Hide comment
@davezuko

davezuko Jan 2, 2016

Contributor

I've had good success following the pattern mentioned in the docs, which is exactly what you describe with redux-thunk. Perhaps there are things that I'm missing out on, but it works especially well for operations such as only making a request if the redux cache is stale.

From: http://rackt.org/redux/docs/advanced/ExampleRedditAPI.html

function shouldFetchPosts(state, reddit) {
  const posts = state.postsByReddit[reddit]
  if (!posts) {
    return true
  } else if (posts.isFetching) {
    return false
  } else {
    return posts.didInvalidate
  }
}

export function fetchPostsIfNeeded(reddit) {
  return (dispatch, getState) => {
    if (shouldFetchPosts(getState(), reddit)) {
      return dispatch(fetchPosts(reddit))
    }
  }
}
Contributor

davezuko commented Jan 2, 2016

I've had good success following the pattern mentioned in the docs, which is exactly what you describe with redux-thunk. Perhaps there are things that I'm missing out on, but it works especially well for operations such as only making a request if the redux cache is stale.

From: http://rackt.org/redux/docs/advanced/ExampleRedditAPI.html

function shouldFetchPosts(state, reddit) {
  const posts = state.postsByReddit[reddit]
  if (!posts) {
    return true
  } else if (posts.isFetching) {
    return false
  } else {
    return posts.didInvalidate
  }
}

export function fetchPostsIfNeeded(reddit) {
  return (dispatch, getState) => {
    if (shouldFetchPosts(getState(), reddit)) {
      return dispatch(fetchPosts(reddit))
    }
  }
}
@arunoda

This comment has been minimized.

Show comment
Hide comment
@arunoda

arunoda Jan 3, 2016

Thanks. I followed exactly something like this.
We' can close this issue now.

arunoda commented Jan 3, 2016

Thanks. I followed exactly something like this.
We' can close this issue now.

@arunoda arunoda closed this Jan 3, 2016

@ColCh

This comment has been minimized.

Show comment
Hide comment
@ColCh

ColCh Feb 20, 2016

@arunoda @davezuko why not using Smart components for it? Looks pretty good.

ColCh commented Feb 20, 2016

@arunoda @davezuko why not using Smart components for it? Looks pretty good.

@sompylasar

This comment has been minimized.

Show comment
Hide comment
@sompylasar

sompylasar Feb 20, 2016

@ColCh Because "smart" (actually, "container") components are just thin wrappers that connect other views to the model (which in Redux is the reducer, and action creator thunks), but business logic should be in the model.

sompylasar commented Feb 20, 2016

@ColCh Because "smart" (actually, "container") components are just thin wrappers that connect other views to the model (which in Redux is the reducer, and action creator thunks), but business logic should be in the model.

@gkrinc

This comment has been minimized.

Show comment
Hide comment
@gkrinc

gkrinc Jun 23, 2016

So is the consensus that business logic should exist in the action creators?

gkrinc commented Jun 23, 2016

So is the consensus that business logic should exist in the action creators?

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Jun 23, 2016

Contributor

It's reasonable to put it in both action creators and reducers, depending on your setup and how you want to divide things. See the answer on this topic in the Redux FAQ: http://redux.js.org/docs/FAQ.html#structure-business-logic .

Contributor

markerikson commented Jun 23, 2016

It's reasonable to put it in both action creators and reducers, depending on your setup and how you want to divide things. See the answer on this topic in the Redux FAQ: http://redux.js.org/docs/FAQ.html#structure-business-logic .

@ezhikov

This comment has been minimized.

Show comment
Hide comment
@ezhikov

ezhikov Sep 6, 2016

Did put my logic to specific middleware. Not sure, if it is "best practices", but works well by now.

ezhikov commented Sep 6, 2016

Did put my logic to specific middleware. Not sure, if it is "best practices", but works well by now.

@steodor

This comment has been minimized.

Show comment
Hide comment
@steodor

steodor Dec 15, 2016

How about in a redux-saga? It's overkill in simple cases but if you have to manage multiple sync and async validations and/or have a flow like validate these fields only if the previous async validation succeded etc., then it becomes a viable solution.

steodor commented Dec 15, 2016

How about in a redux-saga? It's overkill in simple cases but if you have to manage multiple sync and async validations and/or have a flow like validate these fields only if the previous async validation succeded etc., then it becomes a viable solution.

@kiss90benedek

This comment has been minimized.

Show comment
Hide comment
@Vanuan

This comment has been minimized.

Show comment
Hide comment
@Vanuan

Vanuan Nov 26, 2017

There's a third option: middleware + dedicated business logic objects:

// call it when needed (e.g. componentDidMount)
dispatch({ type: 'BUSINESS_ACTION', name: 'showSomethingUseful', params: { entityId: props.match.entityId } });

// call it in middleware
handlers[action.name]({ store, webApi, params: action.params });

// when it's called:
handlers = {
  'showSomethingUseful': async ({ store, webApi, params }) => {
    // show some spinners
    store.dispatch({ loading: true });
    try {
      // load some data
      const data = await webApi.load(params.entityId);
      // do some business logic
      const logic = new Logic(data);
      const displayFields = { field: logic.calculateSomething() };
      // display some data
      store.dispatch({ loading: false, data: displayFields });
    } catch(e) {
      // display some errors
      store.dispatch({ loading: false, error: e });
    }
  }
}

Here we divide our state into "UI" state and "Business" state. UI state is things like routes, client side filtering, menus opened, forms filled, options selected, etc. Business state is data from db along with calculated fields, errors, loading state and validation messages.

UI state is kept in redux store, while business state is calculated on the fly + some caching mechanism implemented in web api client.

Any feedback on such approach? Is it against the functional approach? Any caveats?

Vanuan commented Nov 26, 2017

There's a third option: middleware + dedicated business logic objects:

// call it when needed (e.g. componentDidMount)
dispatch({ type: 'BUSINESS_ACTION', name: 'showSomethingUseful', params: { entityId: props.match.entityId } });

// call it in middleware
handlers[action.name]({ store, webApi, params: action.params });

// when it's called:
handlers = {
  'showSomethingUseful': async ({ store, webApi, params }) => {
    // show some spinners
    store.dispatch({ loading: true });
    try {
      // load some data
      const data = await webApi.load(params.entityId);
      // do some business logic
      const logic = new Logic(data);
      const displayFields = { field: logic.calculateSomething() };
      // display some data
      store.dispatch({ loading: false, data: displayFields });
    } catch(e) {
      // display some errors
      store.dispatch({ loading: false, error: e });
    }
  }
}

Here we divide our state into "UI" state and "Business" state. UI state is things like routes, client side filtering, menus opened, forms filled, options selected, etc. Business state is data from db along with calculated fields, errors, loading state and validation messages.

UI state is kept in redux store, while business state is calculated on the fly + some caching mechanism implemented in web api client.

Any feedback on such approach? Is it against the functional approach? Any caveats?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment