eXigentCoder / dobby

A monorepo for a collection of useful services to aid development.
MIT License
0 stars 0 forks source link

Templating System Design #3

Open liquidcowgithub opened 4 years ago

liquidcowgithub commented 4 years ago

Templating System Design Context

So, let's start capturing some thoughts about how the Templating System within dobby should be set out.

So to state an end goal and justification for the existence of a templating system:

The end goal is having an HTML templating service, that's compatible and easy to pipe into the PDF generation service.

The PDF generation service could get its raw HTML from another service in the given project's ecosystem, but it would be great if we could have a complimentary system within dobby that can easily bind and validate models to template strings to produce HTML that can be piped into the PDF generation service to pop out a PDF.

A few questions pop out from this:

Proposal

I propose a very simple arbitrary string templater package to be at the core of the templating system. Let's call it TemplaterCore for now until we come up with a better naming convention for these core utility packages. It should have a single method, with the following signature: .bind(template, model, schema)

The template would be the string template that the model properties are bound to, once validated by the schema.

Dobby has the space to be opinionated, so I think we can impose some sensible ideas on what is considered a template, a model, and a schema. Discussed below.

This TemplaterCore package could then be imported independently into another project that's looking to use the "useful bits" from dobby, or be imported into the next layer of debby; something like a simple string templating API, say TemplaterAPI for now. It's still early days, but this templater api would essentially wrap up the calls to TemplaterCore. TemplaterAPI could then be enhanced with plugins and adaptors to provide different transport mechanisms (REST, SOAP, GraphQL etc.), and provide certain storage opportunities such as a plugin to add persistance of template strings and schemas for a given domain.

Getting towards our end goal, another conceptual plugin could be an HTML validator (after hook middleware).

In the next layer of dobby, this TemplaterAPI could be bundled into a variety of different pre-packaged docker images with different plugins configured to different specialisations on the general concept of binding a model to a template string.

In the outer most layer, a simple CRUD admin portal could be build on top of one of the transport layers (REST probably) to provide simple backend administration of the templates and schemas in the templating API. I think that React Admin is a good candidate for this, but it's a long way away still.

TemplaterCore

A template

I think it makes sense to use a widely used template syntax that will be accessible to most most developers such as Moustache or Handlebars. Whilst templating engines like Razor are very powerful, fast, and feature rich, the knowledge (or up skilling) required to use them is heavily tied into the rest of the tech stack (i.e. .Net).

Whilst a high performance string templater for dobby would be great, it's a secondary concern to accessibility and ease of use in context of the objectives of dobby.

Handlebars gives some nice benefits around the concept of pre-compling a template, but that may dilute the simplicity of the interface. I don't want to discard this idea, but for now, I want to try keep things simple as possible.

So, I suggest simple Moustache template strings. e.g.

const template = "Hello {{name}}";

A model

Quite simply, this is an arbitrary object. e.g.

const model = { name: "bob" };

A future enhancement on the '.bind()' method could be to allow for both objects and a string as the model argument, and then internally attempt to parse it into an object with JSON.parse(model). However, we then need to consider the error handling for model parsing failure over and above schema failure discussed below.

A schema

This is where I think we can add a bit of a safety net to the templater, and justify its existence wrapping what is essentially moustache.js. Essentially dobby's templater could perform model validation.

We could consider making the schema argument optional, in which case the validation step is bypassed, but I think there is some value in forcing some validation?

For the actual schema definitions, I suggest JSON Schema, because when paired with a solid underlying validator like ajv we can get consistent and rich error messages that can still be user defined to a degree.

So, for example:

const schema = {
  "$id": "http://example.com/schemas/schema.json",
  "type": "object",
  "properties": {
    "name": { "$ref": "defs.json#/definitions/str" }
  }
};

Example usage of templaterCore

An example of usage could be:

import {templaterCore} from "dobby";

const template = "Hello {{name}}";

const model = { name: "bob" };

const schema = {
  "$id": "http://example.com/schemas/schema.json",
  "type": "object",
  "properties": {
    "name": { "$ref": "defs.json#/definitions/str" }
  }
};

const stringResult = templaterCore.bind(template, model, schema);

TemplaterAPI

todo

Templater Plugins

todo

Templater Admin Portal

todo

liquidcowgithub commented 4 years ago

Started prototyping this out in a PR, https://github.com/eXigentCoder/dobby/pull/4

Tweaked the style a bit; so instead of newing up a TemplaterCore, usage is now just directly importing the bind method and invoking it. Example usage:


import { bind } from "templater-core";

const template = "Hello {{name}}!";

const model = { name: "bob" };

const schema = {
  $id: `http://example.com/person.json`,
  "type": "object",
  "properties": {
    "name": { "$ref": "defs.json#/definitions/str" }
  }
};

const stringResult = bind(template, model, schema);
// "Hello bob!"

Internally, used AJV and handlerbars as I was enticed by the idea of precompiling the templates and validators.

eXigentCoder commented 4 years ago

Looking great so far, wanted to just quickly capture some thoughts around this:

  1. Do you feel that the .bind should be responsible for the validation of the model against the schema? I feel that model validation is something that will be used in nearly every other service and warrants being moved out in one of two ways:
    1. Either keep the model + schema on the .bind and TemplaterCore delegates the validation to ValidationCore
    2. Use chaining higher up so that only validated models are passed in to .bind
    3. My personal preference at this stage is for the .bind to have the single responsibility of generating the output and not validation, though I'd love to hear your thoughts around this?
  2. Totally agree on JSON Schema in combo with Ajv have used them for years and they are amazing
  3. In terms of admin ui's I have been looking at AdminBro but haven't had a chance to use it yet
  4. Do you think .generate might be a bit more intent revealing than .bind?