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)
_, _ -> Nothingwhich describes the following mapping
-- | case 1
{ updateTitle : Just { title }
, updateContent : Nothing
}
-> PostUpdateTitle { title }
-- | case 2
{ updateTitle : Nothing
, updateContent : Just { content }
}
-> PostUpdateContent { content }issues
- if all resolvers of a child
ObjectTypedon't require context, we can leavectx :: Typeas a universally quantified Type variable (forall ctx.)
- but currently, this would raise a compiler
TypeErrorbecause type class constraintObjectType ctx a =>enforces all childObjectTypebounded by the same existentialctx :: Type
improvement plan
- use
Genericrepresentation of ADTs to automatically deriveGraphQLType- Required/Optional
- by default, all fields are required/non-null
nonNull <<< _
Maybe->Nullable
- by default, all fields are required/non-null
ScalarTypeInt->GraphQLIntNumber->GraphQLFloatString->GraphQLStringBoolean->GraphQLBooleanGraphQLID?newtype ID = ID String- extra configuration, takes a
Symbolas the field name for ID
Array->ListTypeSum->GraphQLUnionTypeSumType where all the constructors are nullary ->GraphQLEnumType
GraphQLInterfaceType- not necessary with PureScript's type system
Record->GraphQLObjectTypeorGraphQLInputObjectType- derive constructor to
- distinguish
GraphQLObjectTypefromGraphQLInputObjectType - further inject (optional) descriptions and resolvers
- distinguish
- derive constructor to
- Required/Optional
- mapping from Fold (auto-derivable from Haskell Lense) to GraphQL query tree
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
resolveTypefunction is required when defining an Interface)
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) }
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 Situationextensible 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
clientMutationIdinput 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.
1.Apollo
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.
Lighter-than-air node.js server framework
- pure, functional, Promise-based route handlers
- composeable json body parsing
con: currently no GraphQL middleware
2.HTTPure
3.Hyper
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 likeAddress, down to simpleSwissZipCodeextendingAbstractNumbericZipCode.
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 = NumericZipcodeIn the contrast, just two levels of type granularity are visible in the SQL database:
- relation type created by you, like
UsersandBillingDetails- 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
Addresstype (class) in Java and a newADDRESSSQL 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 );
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.
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
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
walking the object network lazy loading
minimize the number of queries to the database
SpringMVC: synchronous, one-request-per-thread WebFlux: concurrent; ReactorStream(RxJava), Netty
Simon Meier / The Service Pattern
No Template Haskell
Template Haskell
Persistent :: Yesod Web Framework Book- Version 1.6
Template Haskell
Working with databases using Groundhog - School of Haskell
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
ToJSONandFromJSONfrom derived instances ofGenerictype 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
ToJSONandFromJSONby 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_" ++)})
''Personmanually define instances of
ToJSONandFromJSONfor 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_
-- | 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
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