Skip to content
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

feat: data engine refactoring #115

Merged
merged 30 commits into from
Sep 16, 2019
Merged

feat: data engine refactoring #115

merged 30 commits into from
Sep 16, 2019

Conversation

amcgee
Copy link
Member

@amcgee amcgee commented Sep 10, 2019

TRIGGER WARNING: This is not a small PR

This pull request is a major overhaul of the internal architecture of the data service. It supersedes both #66 and #102

The following are accomplished in this PR:

  • Add support for mutations (similar to feat: add support for mutations #66 and WIP: refactor query/mutation internals #102)
  • Separation of concerns between Data Engine, resource resolvers (links), and React APIs
  • Adds a useDataEngine hook to get direct access to the engine which supports .query and .mutation methods
  • Adds an engine prop to the response objects of both useDataQuery and useDataMutation, to ensure
  • Adds refetch callback to useDataQuery (from feat: add support for mutations #66)
  • 94% unit test coverage (the untested bit being the re-export file)
  • Adds a full example application with dynamic queries (pagination), create/update/delete functionality which hits a live DHIS2 backend.
  • Moves query parameters into a dedicated params property, which is a breaking change but gives us much more stability and flexibility, and eliminates the potential for param naming conflicts (i.e. with the resource field)

These are the things I like about this change:

  • API near-parity,
  • Better functional isolation between components
  • Significantly better test coverage as a result of that isolation
  • Expansion potential to add and pipeline different resolver links (for caching and de-duplication, for instance, as well as automatic API version toggling)
  • Exposition of an imperative engine which can be used outside of React component bodies (note: the engine still needs to originate from a React context, it is not a singleton by design)

Things I don't love about this change:

  • Not insignificant increase in complexity - instead of everything living in one place and being pretty simple to reason about, we now have 3 layers of abstraction (which gives us a lot of benefits, but increases cognitive load for contributors and debuggers)
  • I worry about proliferation of data when adding the imperative interface - it will be tempting (or unconscious) to request data through the engine and then stash it somewhere (like a Redux store). This is not a good pattern, because it means duplication of memory and dilution of the "single source of truth" principle. It also makes it much harder to add caching in the future since we seed some control of the datastore.
  • Basically this re-invents Apollo but without GraphQL. If at some point in the future we add backend support for GraphQL, this will be wasted effort and complexity. It might be possible to implement our API access layer as a Link in Apollo, but that involves using GQL for data queries which might be a big ask.
  • Some bundle size bloat - this change brings bundle size from 12kb to 16kb, which is probably worth it for the features we're adding.

Layer Consumer Context Examples Description
API React >= 16.8 DataProvider, useDataQuery, useDataEngine, useDataMutation, CustomDataProvider The user-facing API for applications and component libraries to access DHIS2 data. Uses React Context to initialize the Data Engine and Link, isolate application configuration, and disseminate engine access to deeply nested components.
Data Engine Javascript engine.query(), engine.mutate() Processes queries and mutations and passes them to the underlying Link, returning a promise resolving to the response
Data Engine Links Javascript CustomDataLink, RestAPILink, CacheLink (future) A link does the actual data fetching once the engine has parsed the query or mutation. Right now a single link will either generate an HTTP request or look up data in a static map, but eventually links could be chained together to do things like deduplication and cache lookups

These layers are functionally isolated in code, so there aren't any cross-layer imports (except a few Typescript types). We could split out the engine and links to separate repositories, but that will just increase the complexity at this point so I think we should defer that move until we have a reason to use the engine stand-alone.


TODO

  • Updated documentation
  • Example showing recommended use with Redux (with and without imperative calls)

@amcgee amcgee requested a review from varl September 10, 2019 16:49
@amcgee amcgee changed the title feat: internals overhaul feat: data engine refactoring Sep 11, 2019
Copy link
Contributor

@varl varl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The technical parts of this PR looks good, it makes sense to keep the React API separate from the internals.

While it's nice to have the imperative API ready, we need to have documentation for the intended use case for it so we don't start to overuse it early and lose the benefits of declaring data up front. That would put the data engine more along the lines of D2 which was easy to leak throughout an applications architecture.

Stating the technology as "Javascript" in the matrix for the Data Engine and Data Engine Links layers should be "Typescript".

All in all I'm OK with this, I think it is time that we start applying it in the "real world" before extending it further. Especially in the refactored Maintenance app as if the Data Engine cannot deal with those use cases, then it limits its utility in the wild.

One of the most popular type of apps that is built by external developers is a maintenance app specific for their metadata and criteria.

children,
}: MutationInput) => {
const mutationState = useDataMutation(mutation, {
onCompleted,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why choose onCompleted over onComplete?

Copy link
Member Author

@amcgee amcgee Sep 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷‍♂ I'd usually prefer on<verb>, but this came from Apollo Client which uses onCompleted for this exact case. Our react hooks API is almost identical to Apollo's at the moment, which I think is a nice feature. Do you think it should be onComplete?

If we want full parity another consideration is whether to use client instead of engine - I prefer engine there though I think.

@amcgee
Copy link
Member Author

amcgee commented Sep 16, 2019

Stating the technology as "Javascript" in the matrix for the Data Engine and Data Engine Links layers should be "Typescript".

I disagree with this - the source is typescript, but the output is javascript and those layers can be consumed in any javascript context, rather than a react context. There is no dependency on typescripts for the consumer.

@varl
Copy link
Contributor

varl commented Sep 16, 2019

Stating the technology as "Javascript" in the matrix for the Data Engine and Data Engine Links layers should be "Typescript".

I disagree with this - the source is typescript, but the output is javascript and those layers can be consumed in any javascript context, rather than a react context. There is no dependency on typescripts for the consumer.

Fair enough, I took the matrix as internal docs, not external so when using the matrix as a map for the review it stuck out as odd to me.

@amcgee
Copy link
Member Author

amcgee commented Sep 16, 2019

Fair enough, I took the matrix as internal docs, not external so when using the matrix as a map for the review it stuck out as odd to me.

Changed to Consumer Context in this PR

@amcgee
Copy link
Member Author

amcgee commented Sep 16, 2019

All in all I'm OK with this, I think it is time that we start applying it in the "real world" before extending it further. Especially in the refactored Maintenance app as if the Data Engine cannot deal with those use cases, then it limits its utility in the wild.

I agree wholeheartedly with using this in "real world", but I might urge caution in rushing to battle with maintenance-app and its variants. We can (and possibly should) provide a metadata-specific API for DHIS-heavy things (a la D2 schemas, though obviously take that comparison with a grain of salt), so trying to implement maintenance tools around a low-level data access API might lead us to "extend it further" too early. Even though there have been some 3rd party metadata management tools, I would argue actually that this is a special case of app, rather than the status quo.

@amcgee
Copy link
Member Author

amcgee commented Sep 16, 2019

@varl renamed onCompleted to onComplete

@amcgee
Copy link
Member Author

amcgee commented Sep 16, 2019

If approved I'll add docs and merge to next

@varl
Copy link
Contributor

varl commented Sep 16, 2019

I agree wholeheartedly with using this in "real world", but I might urge caution in rushing to battle with maintenance-app and its variants.

The Maintenance App Refactor is a good candidate for that reason -- there's no real timeline to getting it "done" and retire the old maintenance app, so it is not strictly the "real world", but close enough to work as proving grounds.

@amcgee amcgee mentioned this pull request Sep 16, 2019
14 tasks
@amcgee
Copy link
Member Author

amcgee commented Sep 16, 2019

Merging this into next now, we can keep track of the remaining tasks for the v2 release in #125. Thanks @varl again for the review!

@amcgee amcgee merged commit 0186428 into next Sep 16, 2019
@amcgee amcgee deleted the feat/overhaul branch September 16, 2019 11:12
@Mohammer5
Copy link
Contributor

I would've preferred if this merge happened after the meeting (as I've had some questions).
Could we revert this? (Worst case we just need to merge it again)

@varl
Copy link
Contributor

varl commented Sep 16, 2019

@Mohammer5 it's merged to the next branch, there is still time for questions and changes before it is merged to master.

@amcgee
Copy link
Member Author

amcgee commented Sep 16, 2019 via email

@Mohammer5
Copy link
Contributor

@amcgee Thanks, I'll wait for the meeting before I comment on this one

amcgee added a commit that referenced this pull request Sep 24, 2019
* feat: data engine refactoring (#115)

* feat: support network request aborting

* fix: also include alert() in DataQuery renderprop

* fix: don't expose imperative abort, refactor useDataQuery

* feat: add ability to refetch data, upgrade react, tests

* chore: run yarn autoclean for all type deps

* feat: implement mutation hook

* feat: add Mutation component and remove support for concurrent mutations

* chore: mutation tests and bugfix

* chore: fix bad merge

* feat: add support for variables, callbacks, multiple mutations, and more

* chore: refactor internals

* fix: update fetchData call to use object parameter

* fix: remove unnecessary immediate check

* chore: update mutation static input label

* feat: initial overhaul commit

* chore: fix bugs, all tests pass, example works

* chore: full test coverage

* feat: add engine output to hooks, update component wrappers

* chore: fix tests

* chore: update types reference

* chore: fix capitalization typo

* chore: build before testing

* chore: export useDataEngine, move dev deps to root

* chore: rename onCompleted to onComplete

* chore: don't collect coverage from reexport index

* chore: re-indent package.json
dhis2-bot added a commit that referenced this pull request Sep 24, 2019
# [1.6.0](v1.5.1...v1.6.0) (2019-09-24)

### Features

* mutations, dynamic queries, engine refactor ([#127](#127)) ([8677972](8677972)), closes [#115](#115)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants