Description

Mobile Order Book

Trading Application with embedded Order Book with responsive design.

Demo

Check it out: https://trading-platform.vercel.app/

Quickstart

  • yarn Install dependencies

Local development:

  • yarn start Start dev server with hot reloading
  • yarn sanity Run all important checks to validate your changes (test/lint/e2e/build)

Production:

  • yarn build Build production version
  • yarn start:prod Start production server

E2E and Integration tests:

  • yarn e2e Builds default project and runs headless cypress e2e tests

Unit tests:

  • yarn test Runs all unit tests from projects and libraries.

Structure

Structure Graph

Breakdown:

  • Apps – Applications which consume libraries to produce ‘end’ product like Trading App variant with embedded modules.
    Can import and embed modules. Either by static or dynamic imports (module federation). Apps can only implicitly include other apps (cypress e2e is one example). Apps cannot be imported by other parts. Apps can import all other parts. Apps also include e2e testing which is core testing flow used in this app.

  • Modules – Modules are encapsulated functionality which can be embedded in the apps (implicitly or explicitly). In the future we could support webpack federation and each module would be build separately.

  • Model – Data modeling. Can be imported by other models or modules.

  • Data – Data service layer. Responsible for data retrieval/sending for various communication methods. One example is Streaming data layer using WebSocket.

  • Utils – Small reusable chunk of code not falling into other concepts. Small utility/helper functions. Should not import from any other libs. Can be used by all of them.

  • UI – Contains library of ui components. Follows Atomic Design pattern.

Eslint rules ensure that invalid imports are not possible.
Check @nrwl/nx/enforce-module-boundaries inside .eslintrc.json to learn more about constraints.

Testing

E2E and Integration tests

Majority of testing is done in cypress with mocked WebSocked server for various scenarios.
This allows for extensive coverage combined with ease of creating tests with available live preview.

Tests are included as part of separate app called app/trading-platform-e2e which implicitly depends on trading-platform app.

Coverage:
Coverage

Unit tests

Specific functions are covered using unit tests written in jest and using testing-library as support.
Styled components are tested using snapshotting. Not ideal in all scenarios, but for styled components works great as we need to
focus on generated css definitions, which are small and easy to compare.

Performance

For CPU/Memory usage, used built it devtools monitoring tool. No issues found for prolonged app usage.

CPU/Memory Monitor

Scale of repaints was taken into consideration. Using width change on row caused smaller repaint areas then transform/gradient/background-size solutions.
Amount of layouts changes in all solutions was similar.

img.png

Performance testing was done on various devices listed below.
To get stable FPS values, I’ve built FPS tool which also forces each frame to be redrawn.
Without it, browsers can allocate frames as they “please” end do not try to run at constant 60fps.

In addition to performance tests using FPS monitoring, I’ve also validated cpu measurement for reducers and react ‘jobs’ using Performance DevTools.

Main cost post reducing and react handling is layout and painting.
Ways of trying to limit layout cost using css gradients, css background size fills or transforms, didn’t produce any value and increased repaints to whole grid.

Another analysis done was checking impact of key for rows being defined as index vs price. Price as key, produced worse results for order book scenario due to amount of changes in a grid and simple rows it’s more performant to actually use index.

Also, tried to limit amount of re-layouts caused by simple text update. Added overflow: hidden where possible to at least simplify
Browser layout calculations. To fully get rid of it, I would need to change strategy to calculate each position manually, and it seems
like doing that seems like an overkill. However, if grid was intended to scale to thousands of items, different strategy would be used then
current one (virtualization, canvas, manual position calculation and transforms).

Alternative option could also be rendering size bars using canvas. But this seems like overkill for a problem, so didn’t pursue it.

If I had more time, I would play with idea of trying out different performance strategies based on rows count. There might a point
where some approach have advantage over another.

Another idea for later would be splitting up rendering steps into two. However, not sure how to do it
without affecting business requirements/sorting, etc. But it would help on low-end devices sometime struggling to handle
bigger updates redrawing effectively every row/cell.

Tech Stack

  • NX – project monorepo setup bootstrapping
  • Yarn – node modules package management
  • React – Frontend rendering library
  • Typescript – Building and strong typing
  • Styled Components – CSS in JS
  • Redux – State management
  • Redux-Toolkit – Helper functionalities for State Management
  • Redux-Toolkit Query – Redux ‘framework’ for defining standardized redux/store handling for data fetching and streaming.
  • WebSocket – Data streaming
  • Jest – Unit tests
  • Cypress – E2E and Integration tests

Tested Devices

Moto G7 Play | Low End | 2019

Stable 60fps.

Phone

Pixel 2 | 2017

Stable 55-60fps.

Phone

Redmi Note 8 | 2019

Stable 60fps.

Phone

Samsung s8 | 2017

Stable 55-60fps.

Phone

Samsung s9 | 2018

Stable 60fps.

Phone

Dependency Graph

You can run below command to see dependency graph for the whole project

yarn nx dep-graph --watch=true

Then visit: http://127.0.0.1:4211

Structure Graph

Available scripts commands

  • start – Start development watch server
  • start:production – Start production server. Use to test production builds
  • sanity – Runs all validation checks (unit/e2e/builds/lint)
  • build – Build production version of default app which is currently trading-platform app
  • lint – Run lint on all projects and libraries
  • test – Run all unit/jest tests from all projects and libraries
  • test:coverage – Build unit/jest coverage for all projects and libraries
  • e2e – Build production version of default app and run e2e tests headless-ly for it
  • e2e:watch – Start development server with watch and run e2e in window mode
  • e2e:coverage – Generate coverage for e2e tests on production version of default app
  • coverage – Build and merge coverage for all projects and libraries. Currently, e2e is not merged due build setup differences between app and libs

You can also use all available nx commands for managing and working with monorepo workspace.

GitHub

View Github