Skip to content

Instantly share code, notes, and snippets.

@krisleech
Last active April 7, 2017 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krisleech/ca2da962d5e46d5f3e4ba7a4a56082b9 to your computer and use it in GitHub Desktop.
Save krisleech/ca2da962d5e46d5f3e4ba7a4a56082b9 to your computer and use it in GitHub Desktop.

commands

{
  id: "assign-person-to-study",
  url: "/commands/:id",
  method: "POST",
  fields: [
    { key: "study_id", type: "integer" },
    { key: "title", type: "string", select: true, collection: ["Dr", "Mr", "Mrs", "Miss"], validate: { presence: true, included: :collection } },
    { key: "first_name", type: "string", validate: { presence: true, length: 255 } },
    { key: "last_name", type: "string", validate: { presence: true, length: 255 } },
    { key: "email_address", type: "email", label: "Email", validate: { presence: true, uniquness: true } },
    { key: "phone_number", type: "text", label: "Phone", validate: { presence: true } },
    { key: "category_id", type: "integer", select: true, label: "Category", collection: { url: 'people/categories', label: '#name', value: '#id' }, validate: { uniqueness: true } }
  ]
}

We could have basic types, string, integer etc. or richer types such as email which imply certain validations with regards to the shape of the data, i.e. a regex.

The url is where to submit the command too, in this case /commands/:id is a generic controller. Note that :id refers to the command, "assign-person-to-study", not a record id. The URL could also contain references to any of the form data, for example: "/organisation/{{ organisation_id }}/people".

In the case of category_id the collection (possible selections) are to be fetched by the UI from the given URL, a JSON document is expected back and the label and value are the keys within the document to use, they could be specifyed as xpath or css selectors.

The collection will determin the possible values for a field (it will usually, but does not have to be, be rendered as select or autocomplete). Possible configurations to collection include:

static source

collection: { values: ["a", "b", "c"] }
collection: { values: { "a" => "1", "a" => 2, "c" => 3 }

HTTP source

collection: { url: "http://od.documas.eu", root: "#collection", label: "#name", value: "#organisation-code" }
collection: { url: "http://example.co,/service", format: "xml", label: "#name", value: "#id" }

root, label and value are xpath-like selectors.

The url source would work for a JSON document such as:

{
  collection: [ { name: "ABC Corp", organisation-code: "1001PTO" }, ... ],
  count: 102
}

SQL source

collection: { sql: "SELECT name,id FROM people", label: '#name', value: '#id' }

Does the SQL need to have values injected?


{
id: "update-person-assigned-to-study",
inherit: "add-person-to-study",
fields: [ { key: 'id'
}
{
id: "deassign-person-from-study",
fields: [
  { key: "study_id" },
  { key: "person_id" }
  ]
}

forms

id: "assign-person-to-study-form",
command: "assign-person-to-study",
ui: [
    { key: "names", label: "Names", type: "panel", content: [{ field: "first_name" }, { field: "last_name" }] },
    { key: "contact_details", type: "panel", content: [{ field: "email_address} }, { field: "phone_number" }] }
    { key: "save-button", type: "button", submit: true, label: "Save" }
]

content could contain any kind of UI component, not just fields. This allows us to build any kind of UI, not just plain forms.

The above schemas would be fetchable by the UI as JSON and the HTML built client-side or the server could return HTML.

maybe one form can inherit from another:

{
id: "update-person-assigned-to-study-form",
inherit: "assign-person-to-study-form",
}
{
  id: "deassign-person-from-study",
  ui: [
    { type: "field", key: "study_id", type: "hidden" },
    { type: "field", key: "person_id", type: "hidden },
    { type: "button", label: "Delete" }
  ]
 }

This would render as a button. How to put it at the side of a table row?

It might be better to not make this form specific but UI, so:

id: "assign-person-to-study-form",

ui: [
form: { 
  command: "assign-person-to-study",
  fields: [
    { key: "names", label: "Names", type: "panel", content: [{ field: "first_name" }, { field: "last_name" }] },
    { key: "contact_details", type: "panel", content: [{ field: "email_address} }, { field: "phone_number" }] }
    { key: "save-button", type: "button", submit: true, label: "Save" }
   ]
]

This will allow us to use the same system to build any UI, not just forms.

Also one UI config can be embedable in another:

ui: [
  tabs: [
    { name: '...', content: [:assign-person-to-study-form] },
    { ... }
  ]
]

the controller would get params such as:

{
  id: "assign-person-to-study",
  study_id: "ABC123",
  title: "Mr",
  first_name: "Joe",
  last_name: "Bloggs",
  ...
}

The generic controller would then run the validations based on the command schema.

If it is not valid it responds with the errors.

{
  errors: [
    { field: "first_name", message: "is too long" }
  ]
}

If it is valid it creates an event and returns the event id.

{
  event: {
    uuid: "...",
    timestamp: "..."
  }
}

The event id can be used by the UI to know if the event has been processed yet and thus it has fresh data. But how does it know an event has been processed, because there are multiple handlers, it needs to know, thus couple to a handler name, i.e. ask the server has "handler X" processed event id Y. Maybe it would be better to use the timestamp, has the data I have now been updated since the timestamp of the event.

the event might looks like:

{
  name: "person-assigned-to-study",
  uuid: "...",
  timestamp: "...",
  user: "User#123",
  payload: {
    study_id: "ABC123",
    title: "Mr",
    first_name: "Joe",
    last_name: "Bloggs"
  }
}

We might want to reference the command which created it too? Maybe we want to store the commands too, for the ultimate audit, i.e. this is the specific input given to us by user X at Y o'clock. We could even put the commands in the event log, return a HTTP response stright away, e.g. 201 or 202, with he commands UUID (or better, the URL where it's status can be fetched), have a handler pick up the command (instead of the controller) and create an event, other handlers pick up the event and processes it as usual. However we would proberbly need websockets to allow the UI to get notified of the outcome of the command processing, errors or success, since it would be totally async. But this would allow a super generic web layer.

It would be best if every user had a uuid, then no need to specify the class too.

We might also want study_id, to be split in to aggregate_root and aggregate_id to allow us to fetch all events relating to a specific aggregate.

Should we store events with types, integer etc. For example arvo, or edn. Or we store as strings and coherse with schema.

A handler subscribed to the topic, picks up an event and does something.

The topics could be named after the events, e.g. topic per event type, or a single topic called events.

In the above case it would write a new record to the database and maybe send an email to the person being assigned. This could be split in to two handlers since the priority will be different, we want quick writes to the database so the UI can be refreshed.

We could abstract the SQL in to a data configuration instead of having SQL statements inside command and ui configurations. It could be used for collections too, e.g. HTTP source, SQL source etc.

GET /ui/:id => config for UI (rendered client-side using ClojureScript/Reagent)
POST /command/:id => errors or success

How to hook it all up for editting?

data => form => command => event => data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment