You are not logged in. Click here to log in.

Application Lifecycle Management

Search In Project

Search inClear

Onboarding #149119/HEAD / v140
Tags:  not added yet

Onboarding

- software required

See ALM: Requirements

- build steps

See ALM: Windows Development

- local running and testing

See ALM: Help

Type `catena` for a list of commands it can do (or just “yarn run )

- deployment

See ALM: Deployment Cheatsheet


M4 Differences

The main difference between er2_m4 and other OWSI apps is that its data layer is done using ORM Models and a query language. Later, we will see the React code also has separated concerns.

Backend


“Object-Relational Mapping” (ORM) “maps” from a programming language to e.g. SQL statements without writing the SQL statements directly. This is a first-class feature of Django. If you aren’t familiar with Django, the best thing to do would be jump into the examples in the documentation: https://docs.djangoproject.com/en/3.2/topics/db/


This practice of modeling our tables contrasts with picking data out of a single Redis record.


The frontend accomplishes this decoupling by way of a query language. We use GraphQL for this since types and function are idiomatic. GraphQL executes queries using a type system that is completely user-defined for your data.


GraphQL isn't tied to any specific database or storage engine and is instead backed by existing code – in this case Django models.

A GraphQL service is created by defining types and fields on those types, then providing functions for each field on each type. A Mounted Field/Type means that, that type has a resolver function.

In the er2_web workspace, each service's types are then merged into one large schema by a SchemaDelegate - This means our final /graphql/v2/ endpoint will include them.

  • app
    • services
      • schema.py (root type)
        • mounted types (fields)
      • service
        • types.py (types)
          • mounted fields (fields)
        • mutations.py (fields)
          • returned fields (types)


The actual Types are written using a Graphene DSL I’ve developed in er2_graphql. These base classes use introspection to provide implementations for most resolver functions.

class ModelType(django.ObjectType):
   class Meta:
     model = models.Model
class CsipType(csip.ServiceType):
   class Meta:
      path = "m/modelname/field/1.0"

Frontend

We consume this GraphQL API using the Apollo React library in a standard Bulletproof React project.

Read: https://github.com/alan2207/bulletproof-react/blob/master/README.md

--

Following Bulletproof React principles, we should define and export request declarations

For our Apollo code in a “app/features/feature/api” module. Often, this is sidestepped for shorter cognitive overhead – one query next to the component to keep knowledge from noodling. The downside is that we must think more carefully about where in React our queries happen. (example where I have the same name field in 3 queries and it makes mutations not so elegant)


“Requests for GraphQL APIs are declared via queries and mutations that could be consumed by data fetching libraries such as apollo-client. This makes it easier to track which endpoints are defined and available in the application. You can also type the responses and infer them further for a good type safety of the data. You can also define and export corresponding API hooks from there.”


We could be using @apollo/cli to make Typescript types – it’s a larger loop and I just never have - these types would make the feature/api modules have type-completion.


--


I’ve been following a pattern for the api folder that follows:

  • export everything in index.ts as namespaces: export * as Namespace from “./Namespace”
  • put each “thing” in its own module
  • use @reduxjs/toolkit createSlice() api to make a redux reducer and actions
  • export a selectors object with @reduxjs/reselect createSelector() selectors
  • export well-named GraphQL fragments


--

Decoupling Queries

You can use the createProvider API to create a react context that queries the graphQL api. this leaves it up to you to figure out where the most efficient place to render it would be.

You can also simply export hooks from your API in a more traditional Bulletproof manor:

import { gql, QueryHookOptions, useQuery } from "@apollo/client"

const stationListQuery = gql`
  query StationList($include: JSON) {
    stationList(include:$include) {
      name
      availableElements
  }
}
`
export const useStationList = (options: QueryHookOptions) => useQuery(stationListQuery, {
  …options,
})