Giter Site home page Giter Site logo

backendplayground's Introduction

GraphQL

so far, we can only simulate Input Union type by InputObjectType with all fields left optional

adapted from purescript-graphql-example

data PostAction
  = PostUpdateTitle { title :: String }
  | PostUpdateContent { content :: String }

is encoded as

newtype PostAction = PostAction
  { updateTitle :: Maybe PostUpdateTitle
  , updateContent :: Maybe PostUpdateContent
  }
newtype PostUpdateTitle = PostUpdateTitle { title :: String }
newtype PostUpdateContent = PostUpdateContent { content :: String }

and need an explicit translator to enforce mutual exclusion constraint

toPostAction :: PostActionObject -> Maybe PostAction
toPostAction { updateTitle, updateContent } = case updateTitle, updateContent of
  Just arg, Nothing -> Just (PostUpdateTitle arg)
  Nothing, Just arg -> Just (PostUpdateContent arg)
  _, _ -> Nothing

which describes the following mapping

-- | case 1
{ updateTitle : Just { title }
, updateContent : Nothing
}
-> PostUpdateTitle { title }

-- | case 2
{ updateTitle : Nothing
, updateContent : Just { content }
}
-> PostUpdateContent { content }

issues

  1. if all resolvers of a child ObjectType don't require context, we can leave ctx :: Type as a universally quantified Type variable (forall ctx.)
  • but currently, this would raise a compiler TypeError because type class constraint ObjectType ctx a => enforces all child ObjectType bounded by the same existential ctx :: Type

improvement plan

  • use Generic representation of ADTs to automatically derive GraphQLType
    • Required/Optional
      • by default, all fields are required/non-null
        • nonNull <<< _
      • Maybe -> Nullable
    • ScalarType
      • Int -> GraphQLInt
      • Number -> GraphQLFloat
      • String -> GraphQLString
      • Boolean -> GraphQLBoolean
      • GraphQLID ?
        • newtype ID = ID String
        • extra configuration, takes a Symbol as the field name for ID
    • Array -> ListType
    • Sum -> GraphQLUnionType
      • Sum Type where all the constructors are nullary -> GraphQLEnumType
    • GraphQLInterfaceType
      • not necessary with PureScript's type system
    • Record -> GraphQLObjectType or GraphQLInputObjectType
      • derive constructor to
        • distinguish GraphQLObjectType from GraphQLInputObjectType
        • further inject (optional) descriptions and resolvers
  • mapping from Fold (auto-derivable from Haskell Lense) to GraphQL query tree

References

doesn't even explain the differences

Unions are identical to interfaces, except that they don't define a common set of fields. Unions are generally preferred over interfaces when the possible types do not share a logical hierarchy. to query any field on a union, you must use inline fragments

GraphQLInterfaceType is isomorphic to a Product type of

  • a common set of fields (also grouped in a Product type)
  • a Union type for distinct fields (enforced, that's why a resolveType function is required when defining an Interface)

Interface solution, equivalent encoding in Elm by extensible Record

type alias Event a =
    { a
        | id : ID
        , name : String
        , startsAt : Maybe String
        , endsAt : Maybe String
        , venue : Maybe Venue
        , minAgeRestriction : Maybe Int
    }
    
type alias Concert =
    Event 
        { performingBand : Maybe String }

type alias Festival =
    Event 
        { performers : Maybe (List String) }

type alias Conference =
    Event 
        { speakers : Maybe (List String)
        , workshops : Maybe (List String) }
        

Product and Union solution, equivalent encoding in Elm by extensible Record and Union type

type alias Event a =
    { a
        | id : ID
        , name : String
        , startsAt : Maybe String
        , endsAt : Maybe String
        , venue : Maybe Venue
        , minAgeRestriction : Maybe Int
    }
    
type alias Concert = 
    { performingBand : Maybe String }

type alias Festival =
    { performers : Maybe (List String) }

type alias Conference =
    { speakers : Maybe (List String)
    , workshops : Maybe (List String) }

type Situation =
      Concert
    | Festival
    | Conference

type alias SituationalEvent =
    Event Situation

extensible Record (like inheritance in OOP) is not recommended for everchanging model, use named field instead (composition over inheritance)

type alias Event =
    { eventSpec : EventSpec
    , situation : Situation }

type alias EventSpec =
    { id : ID
    , name : String
    , startsAt : Maybe String
    , endsAt : Maybe String
    , venue : Maybe Venue
    , minAgeRestriction : Maybe Int
    }
    
type Situation =
      Concert ConcertSpec
    | Festival FestivalSpec
    | Conference ConferenceSpec

type alias ConcertSpec = 
    { performingBand : Maybe String }

type alias FestivalSpec =
    { performers : Maybe (List String) }

type alias ConferenceSpec =
    { speakers : Maybe (List String)
    , workshops : Maybe (List String) }
    

Remember, the clientMutationId input is required by the mutation; don't worry, we can spoof it when we're interacting with the mutation outside of Relay.

Remove the clientMutationId requirement by creating a new root id for each executed mutation #2349

Mutations

var shipMutation = mutationWithClientMutationId({
  name: 'IntroduceShip',
  inputFields: {
    shipName: {
      type: new GraphQLNonNull(GraphQLString)
    },
    factionId: {
      type: new GraphQLNonNull(GraphQLID)
    }
  },
  outputFields: {
    ship: {
      type: shipType,
      resolve: (payload) => data['Ship'][payload.shipId]
    },
    faction: {
      type: factionType,
      resolve: (payload) => data['Faction'][payload.factionId]
    }
  },
  mutateAndGetPayload: ({shipName, factionId}) => {
    var newShip = {
      id: getNewShipId(),
      name: shipName
    };
    data.Ship[newShip.id] = newShip;
    data.Faction[factionId].ships.push(newShip.id);
    return {
      shipId: newShip.id,
      factionId: factionId,
    };
  }
});

var mutationType = new GraphQLObjectType({
  name: 'Mutation',
  fields: () => ({
    introduceShip: shipMutation
  })
});

Assume data is an Object in global scope which represents a external database and thus any operation (CRUD) on it is an IO.

mutateAndGetPayload sends an mutation IO to the server and receives a payload Object from the server. But after receiving the payload from the "server", namely { shipId, factionId }, outputFields which postprocesses the payload accesses the server (data) again. Very likely an anti-pattern.

Tools

Apollo Client The Query component uses the React render prop API (with a function as a child) to bind a query to our component and render it based on the results of our query. cache for repeated query

Apollo Server

reindex-api is a multi-tenant, hosted GraphQL database solution. reindex-api converts a JSON based schema into a GraphQL API in addition to creating a database storage (MongoDB or RethinkDB) underneath.

Language binding

Node

ORM

flow-typed/sequelize_v4.x.x

graphql-sequelize

objection-graphql

Web Framework

Lighter-than-air node.js server framework

  • pure, functional, Promise-based route handlers
  • composeable json body parsing

con: currently no GraphQL middleware

Purescript

Database

Web Server (API only)

Java

Spring

Database ORM

Object-Relational Mapping (ORM)

The Paradigm Mismatch

1. abstraction granularity

missing higher-level object abstraction in relational calculus

Classes in the Java domain model come in a range of different levels of granularity: from coarse-grained entity classes like User, to finer-grained class like Address, down to simple SwissZipCode extending AbstractNumbericZipCode.

type alias User =
  { username : String
  , address : Address
  }
  
type alias Address =
  { street : String
  , zipcode : Zipcode
  , city : String
  }
  
type Zipcode
  = NumericZipcode Int
  | LiteralZipcode String
  
type alias SwissZipcode = NumericZipcode

In the contrast, just two levels of type granularity are visible in the SQL database:

  • relation type created by you, like Users and BillingDetails
  • built-in data types, such as VARCHAR, BIGINT, TIMESTAMP
create table USERS 
  ( Username VARCHAR(15) NOT NULL PRIMARY KEY
  , Address ADDRESS NOT NULL
  );

A new Address type (class) in Java and a new ADDRESS SQL data type should guarantee interoperability.

create type ADDRESS as table
    ( Street VARCHAR(255) NOT NULL
    , Zipcode VARCHAR(5) NOT NULL
    , City VARCHAR(255) NOT NULL
    );

User-defined data types (UDT) support is one of a number of so-called object-relational extensions to traditional SQL. Unfortunately, UDT support is a somewhat obscure feature of mast SQL DBMSs and certainly isn't portable between different products. Furthermore, the SQL standard supports UDT, but poorly.

The pragmatic solution for this problem has several columns of built-in vendor-defined SQL types:

create table USERS 
  ( Username VARCHAR(15) NOT NULL PRIMARY KEY
  , Address_Street VARCHAR(255) NOT NULL
  , Address_Zipcode VARCHAR(5) NOT NULL
  , Address_City VARCHAR(255) NOT NULL
  );
2. subtyping

common fields in the superclass

Each of these subclasses defines slightly different data (and completely differnt functionality that acts on that data).

regardless of attached methods, this is a typical use case of Union type

This is a polymorphic association. Similarly, you want to be able to write polymorphic queries, and have the query return instances of its subclasses. SQL databases lack an obvious way ( or at least a standardized way) to represent a polymorphic association.

3. identity

Java defines two different notions of sameness:

  • Instance identity (equality by reference)
  • Instance equality (equality by value)

the identity of a database row is expressed as a comparison of primary key values.

It's common for several non-identical instances in Java to simultaneously represent the same row of the database. for example, in concurrently running application threads.

unified by system-generated global identity

4. associations

The challenge is to map a completely open data model, which is independent of the application that works with the data, to an application-dependent navigational model. a constrained view of the associations needed by this particular application.

Object-oriented languages represent associations using object references

  • directional
  • can have many-to-many multiplicity

in the relational world, a foreign key-constrained column represents an association, with copies of key values.

  • The constraint is a rule that guarantees integrity of the association.
  • many-to-one association

use an extra link table between two entities to represent many-to-many association

5. data navigation

walking the object network lazy loading

minimize the number of queries to the database

Hibernate

MyBatis

HTTP

Database Middleware

Web Framework

SpringMVC: synchronous, one-request-per-thread WebFlux: concurrent; ReactorStream(RxJava), Netty

Kotlin

HTTP

Haskell

Simon Meier / The Service Pattern

Database

No Template Haskell

Template Haskell

Persistent :: Yesod Web Framework Book- Version 1.6

Template Haskell

Working with databases using Groundhog - School of Haskell

Network

JSON

aeson-lens: Lens of Aeson

overloaded string literal - school of Haskell

class IsString a where
    fromString :: String -> a

-- String, ByteString and Text are examples of IsString instances

{-# LANGUAGE OverloadedStrings #-}
a :: String
a = "Hello World"

b :: ByteString
b = "Hello World"

c :: Text
c = "Hello World"

derive instances of ToJSON and FromJSON from derived instances of Generic type class

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE DeriveAnyClass    #-}

data Person = Person
  { name :: String
  , age :: Int
  , occupation :: Occupation
  } deriving (Show, Generic, ToJSON, FromJSON)

data Occupation = Occupation
  { title :: String
  , tenure :: Int
  , salary :: Int
  } deriving (Show, Generic, ToJSON, FromJSON)

generate instances of ToJSON and FromJSON by Template Haskell

{-# LANGUAGE TemplateHaskell #-}

import Data.Aeson.TH (deriveJSON, defaultOptions)
-- The two apostrophes before a type name is template haskell syntax
deriveJSON defaultOptions ''Occupation
deriveJSON defaultOptions ''Person

deriveJSON
  (defaultOptions { fieldLabelModifier = ("occupation_" ++) })
  ''Occupation
deriveJSON
  (defaultOptions { fieldLabelModifier = ("person_" ++)})
  ''Person

manually define instances of ToJSON and FromJSON for each data-type

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson (ToJSON(..), Value(..), object, (.=), (.:), FromJSON(..), withObject)

instance ToJSON Occupation where
  toJSON :: Occupation -> Value
  toJSON occupation = object
    [ title .= toJSON (title occupation)
    , tenure .= toJSON (tenure occupation)
    , salary .= toJSON (salary occupation)
    ]

instance ToJSON Person where
  toJSON person = object
    [ name .= toJSON (name person)
    , age .= toJSON (age person)
    , occupation .= toJSON (occupation person)
    ]

instance FromJSON Occupation where
  parseJSON = withObject Occupation $ \o -> do
    title_ <- o .: title
    tenure_ <- o .: tenure
    salary_ <- o .: salary
    return $ Occupation title_ tenure_ salary_

instance FromJSON Person where
  parseJSON = withObject Person $ \o -> do
    name_ <- o .: name
    age_ <- o .: age
    occupation_ <- o .: occupation
    return $ Person name_ age_ occupation_

library spec

-- | A JSON \"object\" (key\/value map).
type Object = HashMap Text Value

-- | A JSON \"array\" (sequence).
type Array = Vector Value

-- | A JSON value represented as a Haskell value.
data Value = Object !Object
           | Array !Array
           | String !Text
           | Number !Number
           | Bool !Bool
           | Null
             deriving (Eq, Show, Typeable)

class ToJSON a where
  toJSON :: a -> Value
  
class FromJSON a where
  parseJSON :: Value -> Parser a

encode :: ToJSON a => a -> ByteString
decode :: FromJSON a => ByteString -> Maybe a
eitherDecode :: FromJSON a => ByteString -> Either String a


-- | The result of running a 'Parser'.
data Result a = Error String
              | Success a
                deriving (Eq, Show, Typeable)  
-- | Run a 'Parser'.
parse :: (a -> Parser b) -> a -> Result b
parse m v = runParser (m v) Error Success

-- A newtype wrapper for UTCTime that uses the same non-standard serialization format as Microsoft .NET
-- The number represents milliseconds since the Unix epoch.
newtype DotNetTime = DotNetTime {
      fromDotNetTime :: UTCTime
    } deriving (Eq, Ord, Read, Show, Typeable, FormatTime)

Connecting a Haskell Backend to a PureScript Frontend

javcasas/purescript-bridge-tutorial

Web Server

Provides a common protocol for communication between web applications and web servers.

wai-extra: Provides some basic WAI handlers and middleware.

taking as input a description of the web API as a Haskell type.

Servant is then able to

  • check that your server-side request handlers indeed implement your web API faithfully,
  • automatically derive Haskell functions that can hit a web application that implements this API,
  • generate a Swagger description or code for client functions in some other languages directly.

Declaration of routes through DSL and Template Haskell

Actor-based Concurrent System

Akka (Scala/Java)

Scala Actor

Erlang Actor

Scala

HTTP

Web Framework

Ruby

ORM

Elixir

ORM

Web Framework

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.