Closed rivernews closed 3 years ago
After considering UPDATE company label, we found a major issue when visiting company detail page: we cannot locate the right bucket given only the uuid. This has to be solved by some other approaches. Due to the fact that too many troubles are found, we need to review the entire lifecycle of company, in order to figure out all changes required:
✅ 1. Fetch Each company group has its own saga, reducer and actions. Have to fire their action one by one in saga to fetch all of them. Lifecycle 1 will suffice automatically -- we just fire trigger action in auth saga, and the rest will be handled.
🕑 2. Create We will have to place new company in the right bucket. Default to Target. In company form, probably need a custom submit callback to dispatch the right action of right bucket. If not handled, will place all company in "All" bucket.
🕑 3. Update Handled: create in destination group bucket, and remove from current group bucket. However, if we want to change the structure of bucket store, this needs to be rewrite.
🕑 4. Visit
Not handled currently. The company detail page is another route. We rely on only uuid
to pass information between routes, and uses uuid
to retrieve the company. Under the current company bucket store structure, we have no idea which bucket contains this uuid
company. At worst have to traverse alll bucket, which is a no no.
✅ 5. Pagination Handled, since filter will work in their own url namespace.
If we go for this approach, we definitely need to remove the "All Company" tab and all its store, as it interferes with the mechanism, and can mislead that things work while in fact it's broken.
This is to solve the Lifecycle 4 trouble, so we avoid having to find which bucket to look into. If we have a single pool -- single source of truth, we simply pull from this pool. No bucket even needed. What about update? Only matters in the home page, i.e. master page, level, where groups have to be in the right tab.
However we need to mind how pagination work in this approach. When each tab / each company group is fetching their company page(s), they all contribute to a single company pool. Use infinite scroll as an example, new pages will be reduced and merged into single pool. --> Looks like not much concern about this.
Let's review how this approach solves the CRUD operations.
LIST
, so jsonResponse
is a list, not single. Instead of dispatching group company's action, dispatch "pool" redux LIST
action -- success action, to write into local store of pool redux.jsonResponse
. To reuse existing redux toolset, we may define a data model Reference
, which is just a uuid, or string in Javascript. Since this type is so simple, we may not need the data model, actually. Can we just use ad-hoc value?formData
; 2) use previous action's callback func arg to ensure the following is run after company data stored in pool: dispatch group company redux action, pass over list of uuid from jsonResponse
-- we may need to refactor to have access to jsonResponse
in callback func. If we don't define a data model for Reference
, we can directly pass over uuids to group company TRIGGER action.jsonResponse
. Pass over this CREATE saga handler to group company redux factory func.Reference
data model, we do not need API call for it -- only the actual Company
data model needs to. So we may not need its saga. So when creating group company redux, we can refactor code to exclude create saga resources. But this can be done later; implement the main functional part first.When an application is updated, its redux update to application store. But not the "embed" application in company object. Same for all CRUD operations, and for applications & application status links.
jsonResponse
, store relational data in their redux store by their SUCCESS action.
Since we are doing normalization, we need to cover a wide range of saga handler, actually even all of them. The main challenge is having to accessing redux resource of other objects when in an object redux saga handler. Because in the entities, you might have multiple types of objects normalized and surfaced up.
Ideally, we want the factory to handle this rather than having multiple saga override/done handler. There are several ways to do this and we'll explore them here.
const reduxFactory = (..., normalizeManifest) => {
reduxResources.saga = function*() => {
const jsonResponse = await fetch(...);
// TODO 1: check normalizeManifest arg availability
const normalizedData = normalize(jsonResponse, normalizeManifest.schema);
const objectNormalizeData = normalizedData.entity[normalizeManifest.objectEntityKey];
const relationalNormalizeData = normalizeManifest.relationalEntityKeys.map(key => normalizedData.entity[key]);
// deal with primary object actions
if (crudKeyword == DELETE) {
.... // same as existing
}
else {
.... // same as existing
}
// deal with relational entities actions - for LIST/CREATE/UPDATE/DELETE -
// TODO 2: check normalizeManifest availability
for (let entityName of normalizeManifest.relationalEntityKeys) {
// TODO 3: what kind of CRUD needs to be applied? May not be the same as the primary object
const entitySuccessAction = normalizeManifest.actions[entityName][?crudKeyword?][RequestStatus.SUCCESS].action;
const entityData = normalizedData.entities[entityName];
// TODO 4: how to deal with single/list response?
yield put entitySuccessAction(entityData / { results: entityData });
}
}
}
Case study: company redux operations. Let's see its LIST/CREATE/UPDATE/DELETE scenarios.
Company LIST: ✅ Application | | | ✅Status For company, we should apply LIST/SUCCESS action(listResponse). For application, we should apply LIST/SUCCESS. For status (application status), we should also apply LIST/SUCCESS.
Company CREATE: ✅ Application | | | ✅Status For company, we should apply CREATE action(singleResponse). For application, we should apply CREATE action. For application status, we should apply CREATE action.
Company UPDATE: ✅ Application | | | ✅Status For company, we should apply UPDATE action(singleResponse). For application, we should apply nothing. -- The update is totally company's own business. For application status, we should apply nothing.
Company DELETE: ✅ Application | | | ✅Status | | | ✅ Cascade DELETE del application ⏸ Cascade DELETE del company For company, we should apply DELETE action(singleResponse, formData). For application, we should apply DELETE action(undefined, listResponse) < we may need to change DELETE action to support list (multiple) delete. For application status, we should apply DELETE action(undefined, listResponse)
Generalization for Application / Status?
Yes, as long as the relationship is like Company (primary) <------ Application (relational)
, or primary <----- multiple relational
, then this paradigm will suffice.
We now choose to handle nested field in done saga, and have factory saga handle 1st level rellational data deletion.
The problem is, at the time of done saga, we lose company and application in store. While we have formData
to deliver company data, we lose application in store. If we don't have application, we can not de-normalize statuses. Otherwise you'll have to do a O(n)
filtering on status store.
While implementing Cascade DELETE, we found a circular dependency:
application-redux: ApplicationRestApiRedux
--needs--> appliation-redux: ApplicationNormalizeManifest
application-redux: ApplicationRestApiRedux
--gives--> application-redux: ApplicationActions
(gives action, reducer, saga)
appliation-redux: ApplicationNormalizeManifest
--needs--> application-normalize: ApplicationStatusActions
--needs--> statuses-custom-saga: applicationStatusDoneCreateSuccessSagaHandler
--needs--> application-redux: ApplicationActions
To summarize:
status custom saga
needs "application actions", needs to be created from "application redux". Circular dependency formed.
Problems
jsonResponse
representing the changed data. Also, we haveformData
, which represents the original data.