Wallstreet

Requirements

  • Node12
  • npm
  • Docker (optional)

Notes

  • The backend uses Express, Apollo & GraphQL. the client interacts with the API using Apollo GraphQL client.

  • There is no REST API.

  • Very little processing is being done on the client side, this is mostly because if you have large datasets with lots of pages, client side sorting and pagination can be problematic.

  • The backend uses GraphQL using Apollo with Express. Backend tests are under the __tests__ folder.

  • Frontend is a ReactJS app, no UI components library are being used. CSS is done by hand.

  • Frontend tests using the *.test.js naming convention as is the default for react apps.

Running

In order to run the project, you can either:

  • Download the image from Docker Hub and run it (Recommended).
  • Build the docker image and run the docker container.
  • Build the React app and run it through the backend express server.
  • Or, you can run the React app and the backend separately.

Download the docker image and run the container

# Pull docker image
docker pull alimuzaffar/wallstreet-test

# Run a container mapping our port 5000 to containers port 5000 with name 'ali_test'
# Remove container on exit
docker run --rm -p 5000:5000 --name ali_test -d alimuzaffar/wallstreet-test

# Ensure docker container is running
docker ps

# Check docker logs to make sure service is ready on http://localhost:5000
docker logs ali_test

If you see output like the one shown, you should be able to visit http://localhost:5000 and see everything work:

[0000-00-00T00:00:00.000] [INFO] default - 🚀  Server ready at http://localhost:5000
 Query GraphQL on http://localhost:5000/graphql

To build and run in docker

# Build the image
docker build -t <your username>/wallstreet-test .

# Run a container mapping our port 5000 to containers port 5000 with name ali_test
# Remove container on exit
docker run --rm -p 5000:5000 --name ali_test -d <your username>/wallstreet-test

# Ensure docker container is running
docker ps

# Check docker logs to make sure service is ready on http://localhost:5000
docker logs ali_test

If you see output like the one shown, you should be able to visit http://localhost:5000 and see everything work:

[0000-00-00T00:00:00.000] [INFO] default - 🚀  Server ready at http://localhost:5000
 Query GraphQL on http://localhost:5000/graphql

When you're done, you probably want to stop the docker container:

docker stop ali_test

To run everything under express:

cd client && npm install && npm run build
cd ../backend && npm install && npm start

The server will run on http://localhost:5000 (react app will at root there if it's been built) There will be a GraphQL playground on http://localhost:5000/graphql You can validate that the sorting and filtering is working there as well.

To run the backend and front-end separately.

# Backend code
cd backend && npm clean-install && npm start
# Starts on http://localhost:5000
# GraphQL playground on http://localhost:5000/graphql

# Client side code (ReactJS)
cd client && npm clean-install && npm start
# Starts on http://localhost:3000

The client, a ReactJS app will run on http://localhost:3000 As required you can sort and filter the data on that page.

The API will run on http://localhost:5000 (react app will be there if it's been built) There will be a GraphQL playground on http://localhost:5000/graphql You can validate that the sorting and filtering is working there as well.

Why is the solution flexible (Playing around in the GraphQL Playground)

The DB colums are mapped to the GraphQL types, this allows me to write simple SELECT * type queries and server up the data quickly. These are in the Sqlite3DatabaseDataSource. However, in order to make client side rendering easier for the front-end client, I wrote a fancy query to try to server up the required data as flatly as possible, however the GraphQL structure is still there.

This means that if you want more details, you can probably just type in the db column name under the graphql object and the column will likely show up.

You can try running the query used to populate the client side app there. You'll need to specify the variables as JSON, click "QUERY VARIABLES" buttom left of the screen.

query($sortBy:String, $sortDirection:String, $filterByField:String, $filterByValues:[String]) {
  unique_scores {
    score
  }
  exchange_symbols {
    exchange_symbol
  }
  companies(sortBy: $sortBy, sortDirection: $sortDirection, filterByField: $filterByField, filterByValues: $filterByValues) {
    company_id
    name
    score
    min_price
    max_price
    volatile_score
    exchange_symbol
    unique_symbol
  }
}

Since companies is a result of a JOIN query of all three tables to make client side rendering easier, you may wish to get data in each table specifically. You can view this data by requesting company or company_score or company_price_close note as company, company_score and company_price_close each represent a table, you'll need to specify the columns you want to return. company_price_close returns a list of all closing prices, ordered by date desc.

Try something like this:

{
  companies {
    company_id
    company {
      id
      name
      ticker_symbol
      score_id
    }
    company_score {
      id
      company_id
      date_generated
      dividend
      future
      health
      management
      past
      value
      misc
      total
      sentence
    }
    company_price_close {
      date
      company_id
      price
    }
  }
}

ID's are repeated everywhere so you can verify that the data is correct.

Have fun!

Why the code is simple

As mentioned, the types defined the GraphQL work off a simple DB query.

For example:

I define an Object of type SwsCompany (typeDef.json) and tell GraphQL that when someone asks for companies, simply call the method getAllCompanies() on our db DataSource. The DataSource returns a List. As SwsCompany type fields match column names in swsCompany table, I simply execute a SELECT * from swsCompany query and GraphQL automatically maps each row to SwsCompany type and creates the list.

I also define SwsCompanyScore where fields match all the columns in the swsCompanyScore. I add sws_comapny_score as a field under SwsCompany and tell GraphQL to resolve that field by calling a getLatestFromTable method on the db DataSource. getLatestFromTable basically runs SELECT * FROM swsCompanyScore WHERE company_id = ?, the company_id is passed to the method as an argument from the parent (SwsCompany). This way, without complex joins I can serve up data from tables with in any format we like.

In reality, I used a more complex query, as it allowed me to get the min and max prices for the last 90 days and it allows me to get figure out how volatile a stock is (simply the difference between max and min price). However, there already is a lot of flexibility in the API where you can request only the columns you want (from the front-end) and you can customize how data is fetched in the backend depending on the query sent to the backend (this allows us to avoid slow queries).

From the front-end, we are also able to request a list of all exchanges and snowflake score in one query, something that would otherwise require multiple queries.

DataSource provides an abstraction layer for us, in unit tests, we can point this to a hard coded JSON object as I have, in the MockDatabaseDataSource or you can point it to really any data source you like.

Code is already Kibana ready, the logger can print out in JSON format with an environment variable change. It's also simple to configure this to send the logs over UDP or TCP if we want.

What could be improved

Backend

  • More GraphQL security measures should be added such as
    • Query depth
    • Payload size
  • GraphQL code could be abstracted better which would allow for better testing.
  • Use of any in the code could be removed and replaced with Typescript interfaces.
  • The query could accept more optional arguments to cater for more scenarios such as:
    • Fetching company by id
    • Sorting, filtering, and limits on closing price of companies (note these are straight forward to implement, the code is flexible enough)

Frontend

  • Better break down of components in ReactJS.
    • Companies and Company do too much.
  • Explicit declaration of properties for components using PropTypes
  • Instead of having filters pass state to the parents, handle this through Redux.
  • Use react-hooks instead of passing state to parent
  • Better structure for CSS (right now everything was shoved into App.css)
  • Use Storybook to test and maintain for low level components.