DEPRECATED - Please see elm-slate/test-entities
Entities for testing Slate components, e.g. Query Engine, Command Helper.
Since the Elm Package Manager doesn't allow for Native code and most everything we write at Panoramic Software has some native code in it, you have to install this library directly from GitHub, e.g. via elm-github-install or some equivalent mechanism. It's just not worth the hassle of putting libraries into the Elm package manager until it allows native code.
This repo servers 2 purposes:
- Provide Entity implementations for testing reads and writes of Slate Events
- Example of how to implement Entities
Entities have 3 parts:
- Implementation
- Schema
- Command Processor (API)
The Entity Implementation contains the following:
- Entire Entity - This is the Entire Entity with all of its Properties. All properties are defined as
Maybes
exceptLists
since they can be empty. - Entire Entity Dictionary - This is just for convenience so code that uses the Entity won't have to constantly define this.
- Default Entire Entity - This is also for convenience so Projection code can have default values. Usually,
Strings
are empty,Ints
are invalid values,Dictionaries
areDict.empty
andLists
are empty. This is also used forSchema Migration
. In that case, default values are usually non-empty to support older versions of an Entity. - Value objects - Value Object definitions for all Value Object Properties.
- Entire Entity Shell - An empty Entire Entity as a starting point for mutations. Used internally, but also exported for convenience.
- Entire Entity Encode Function - This function takes an Entire Entity and produces a JSON String.
- Entire Entity Decode Function - This function takes a JSON String and produces Entire Entity (wrapped in a
Result
). - Handle Mutation Function - This function mutates the appropriate Entity in an Entire Entity Dictionary based on the specified Event.
- Mutate - This function mutates a single Entity based on the specified Event.
Here is the Person Entity's module definition (Note here Name
is a Value Object
):
module Slate.TestEntities.PersonEntity
exposing
( EntirePerson
, EntirePersonDict
, DefaultEntirePerson
, Name
, entirePersonShell
, defaultEntirePerson
, entirePersonEncode
, entirePersonDecode
, handleMutation
, mutate
)
The mutation functions signatures are dependent on the structure of the Entity and its relationships.
This function mutates the appropriate Entity in an Entire Entity Dictionary based on the specified Event.
Here is Address Entity's handleMutation
function:
handleMutation : EntireAddressDict -> Event -> Result String EntireAddressDict
handleMutation dict event
It takes an EntireAddress
dictionary, a mutation event and returns a new dictionary wrapped in a Result
.
This is as simple as it gets.
A more complex example is Person's handleMutation
:
handleMutation : EntirePersonDict -> EntireAddressDict -> Event -> ( Result String EntirePersonDict, Maybe CascadingDelete )
handleMutation dict addresses event
Notice that since Person
has a relationship with the Address
Entity it needs the dictionary of Addresses (2nd parameter).
Also, since Person has an ownership relationship (see Person Schema), it returns a tuple where the first is the new Person
dictionary and the second is a Maybe CascadingDelete
that may contain information that describes how to delete an owned Entity.
This function mutates a single Entity based on the specified Event. This is called internally by handleMutation
but is exported in case the App wants to do processing on a single Entity.
Here is Address Entity's mutate
function:
mutate : Event -> EntireAddress -> Result String (Maybe EntireAddress)
mutate event entity
It takes the Event, the Entire Entity to mutate and returns a new Entire Entity.
Doesn't get simpler than this.
Person's mutate
is only slightly more complex:
mutate : Event -> EntirePerson -> EntireAddressDict -> ( Result String (Maybe EntirePerson), Maybe CascadingDelete )
mutate event entity addresses
In addition to the usual suspects, it also takes an Entire Address dictionary. This is because it has a relationship with Address
. An Entity that has relationships will have an extra parameter like this for each relationship.
It also returns an extra return value for optional cascading delete information.
The Person
Entity has the following definition:
type alias EntirePerson =
{ name : Maybe Name
, age : Maybe Int
, address : Maybe EntityReference
}
Here name
is a Value Object:
type alias Name =
{ first : String
, middle : String
, last : String
}
Here address is a reference to another Entity. (Done this way for testing, not necessarily good design.)
The Address
Entity has the following definition:
type alias EntireAddress =
{ street : Maybe String
, city : Maybe String
, state : Maybe String
, zip : Maybe String
}
Entities have properties. So Schema
s define both Entity schemas and Property schemas. See slate-common for more on Schemas.
personSchema : EntitySchema
personSchema =
{ type_ = "Person"
, eventNames =
[ "Person created"
, "Person destroyed"
]
, properties = personProperties
}
- type_ - This is the type
Person
. - eventNames - Event names for creating and destroying a
Person
. - properties -
Person
property schema (see below)
personProperties : List PropertySchema
personProperties =
[ { mtPropSchema
| name = "name"
, eventNames =
[ "Person name added"
, "Person name removed"
]
}
, { mtPropSchema
| name = "age"
, eventNames =
[ "Person age added"
, "Person age removed"
]
}
, { mtPropSchema
| name = "address"
, entitySchema = Just <| SchemaReference addressSchema
, eventNames =
[ "Person address added"
, "Person address removed"
]
, owned = True
}
]
Here mtPropSchema
is a Empty Property Schema that's mutated to make defining Property Schemas terse, i.e. easier.
Person
has the following properties:
name
- The name of the person (Value Object).age
- The age of the person.address
- A Reference to anAddress Entity
(see below).
addressSchema : EntitySchema
addressSchema =
{ type_ = "Address"
, eventNames =
[ "Address created"
, "Address destroyed"
]
, properties = addressProperties
}
- type_ - This is the type
Address
. - eventNames - Event names for creating and destroying an
Address
. - properties -
Address
property schema (see below)
addressProperties : List PropertySchema
addressProperties =
[ { mtPropSchema
| name = "street"
, eventNames =
[ "Address street added"
, "Address street removed"
]
}
, { mtPropSchema
| name = "city"
, eventNames =
[ "Address city added"
, "Address city removed"
]
}
, { mtPropSchema
| name = "state"
, eventNames =
[ "Address state added"
, "Address state removed"
]
}
, { mtPropSchema
| name = "zip"
, eventNames =
[ "Address zip added"
, "Address zip removed"
]
}
]
Here mtPropSchema
is a Empty Property Schema that's mutated to make defining Property Schemas terse, i.e. easier.
Address
has the following properties:
street
- The stree number and name.city
- The name of the city.state
- The state.zip
- The zipcode.
There are 2 types of dictionaries that can be created, Internal and Process.
The purpose of this dictionary is to allow the combining of simple CRUD mutations in higher-level API, e.g. to create Multiple Events
in a Transaction
. It's values are of type CommandPartFunction
.
This dictionary has the following default keys:
- create
- destroy
- add
- remove
Keys create
and destroy
are for Entities. Keys add
and remove
are for Properites. Any property can be excluded in the dictionary. See ignoreProperties
in Slate.TestEntities.PersonCommand
.
Internal Dictionary entries cannot be executed directly. They must be converted to a CommandFunction
which can be done with the Helper function process
.
For usage, see asMultCmds
in Test.App
.
This dictionary is derivitave of the Interal Dictionary
. Each value of this dictionary will create one or more Events
in a Transaction
. It has the same keys but the values are of type CommandFunction
.
For usage, see asOneCmd
in Test.App
.
The Helper module is a set of routines to make developing most Entity Command Processor code easy. If your Entity has simple CRUD mutations, then there is very little code that needs to be written. This can be seen in Slate.TestEntities.AddressCommand
.
It also contains code to make usage of Command Processors easy. See Test.App
.
Common command function parameters.
type alias CommandFunctionParams msg return =
MutatingEventData -> Config msg -> DbConnectionInfo -> InitiatorId -> return
Result of a CommandPartFunction.
type alias CommandPartResults =
( List String, List EntityReference )
Command part that can be combined with other command parts to be executed in a single transition.
type alias CommandPartFunction msg =
CommandFunctionParams msg CommandPartResults
Function that takes a Command, CommandProcessor model and produces an executable Cmd.
type alias CommandToCmdFunction msg =
CommandProcessor.Model msg -> ( CommandProcessor.Model msg, Cmd msg, CommandId )
Command function to create events in a single transition.
type alias CommandFunction msg =
CommandFunctionParams msg (CommandToCmdFunction msg)
Retrieves the specified property schema from a list of property schemas. CRASHES if not found to prevent bad events in the DB.
propertySchema : String -> List PropertySchema -> PropertySchema
propertySchema propName propertySchemas
Create IntenalFunction for "create" event. Not usually used directly but here just in case.
createInternal : String -> EntitySchema -> CommandPartFunction msg
createInternal
Create IntenalFunction for "destroy" event. Not usually used directly but here just in case.
destroyInternal : String -> EntitySchema -> CommandPartFunction msg
destroyInternal
Create IntenalFunction for "add" event. Not usually used directly but here just in case.
addInternal : String -> EntitySchema -> CommandPartFunction msg
addInternal
Create IntenalFunction for "remove" event. Not usually used directly but here just in case.
removeInternal : String -> EntitySchema -> CommandPartFunction msg
removeInternal
Create IntenalFunction for "add" event from an Entity's Properties. Not usually used directly but here just in case.
addPropertyInternal : String -> List PropertySchema -> String -> CommandPartFunction msg
addPropertyInternal entityType propertySchemas propName
Create IntenalFunction for "remove" event from an Entity's Properties. Not usually used directly but here just in case.
removePropertyInternal : String -> List PropertySchema -> String -> CommandPartFunction msg
removePropertyInternal entityType propertySchemas propName
Build default Internal Dictionary except for specified properties.
buildPartsDict : EntitySchema -> List PropertySchema -> List String -> Dict String (CommandPartFunction msg)
buildPartsDict entitySchema propertySchemas ignoreProperties
Build default Process Dictionary except for specified properties.
buildCommandDict : EntitySchema -> List PropertySchema -> List String -> Dict String (CommandFunction msg)
buildCommandDict entitySchema propertySchemas ignoreProperties
Convert an CommandPartFunction to a CommandFunction with an optional Validator by passing it through the CommandProcessor.
process : Maybe (ValidateTagger CommandProcessor.Msg msg) -> CommandPartFunction msg -> CommandFunction msg
process tagger internal mutatingEventData config dbConnectionInfo initiatorId model
Usage
See asMultCmds
in Test.App
.
Combine a List of command part results into a single command part results.
combine : List ( List String, List EntityReference ) -> ( List String, List EntityReference )
combine operations
Usage
See asOneCmd
in Test.App
.
Takes a list of CommandToCmdFunctions and passes them sequentially and iteratively the CommandProcessor Model to produce a List of Cmds and a final CommandProcessor Model.
asCmds : CommandProcessor.Model msg -> List (ProcessToCmdFunction msg) -> ( CommandProcessor.Model msg, List ( CommandId, Cmd msg ) )
asCmds model operations
Usage
See asMultCmds
in Test.App
.
Create mutating event data for a create/destroy event.
createDestroyData : EntityReference -> MutatingEventData
createDestroyData entityId
Usage
See Test.App
.
Create mutating event data for a duration event.
durationData : EntityReference -> MutatingEventData
durationData entityId
Usage
See Test.App
.
Create mutating event data for an add/remove event.
addRemoveData : EntityReference -> String -> MutatingEventData
addRemoveData entityId value
Usage
See Test.App
.
Create mutating event data for a add/remove reference event.
addRemoveReferenceData : EntityReference -> EntityReference -> MutatingEventData
addRemoveReferenceData entityId refEntityId
Usage
See Test.App
.
Create mutating event data for a position event.
positionData : EntityReference -> EntityReference -> Int -> Int -> MutatingEventData
positionData entityId propertyId oldPosition newPosition =
Usage
See Test.App
.