Knotx / knotx

Knot.x is a highly-efficient and scalable integration framework designed to build backend APIs
https://knotx.io
Apache License 2.0
126 stars 26 forks source link

Fragment processing faliure handling - fallback strategies #466

Closed scypio closed 5 years ago

scypio commented 5 years ago

Problem statement Pages may contain multiple knotx snippets. Failure in processing a single snippet may result in the whole page returning 500 Error respononse.

Let's assume that a page contains two snippets:

<knotx-snippet data-knotx-knots="databridge,handlebars" 
  data-knotx-databridge-name="important-service">
      {{#if _result}}
        <h2>{{_result.count}}</h2>
      {{/if}}
</knotx-snippet>

<knotx-snippet data-knotx-knots="databridge,handlebars" 
  data-knotx-databridge-name="unstable-service">
      {{#if _result}}
        <h2>{{_result.count}}</h2>
      {{/if}}
</knotx-snippet>

When databridge / unstable-service fails the whole rendering process is terminated with a 500 Error.

Proposed solution I would like to:

Blank fallback strategy would just skip rendering of the failed snippet altogether. Static fallback strategy would render a static markup defined as a snippet attribute.

For example, should following fragment fail:

<knotx-snippet data-knotx-knots="databridge,handlebars" 
  data-knotx-databridge-name="unstable-service"
  data-knotx-fallback="No data available">
      {{#if _result}}
        <h2>{{_result.count}}</h2>
      {{/if}}
</knotx-snippet>

the text No data available would be rendered as a fallback.

Alternatives I wondered if fallback could be defined as an embedded node,

<knotx-snippet data-knotx-knots="databridge,handlebars" 
  data-knotx-databridge-name="unstable-service">
  <knotx:fallback>
    <p class="warning">No data available</p>
  </knotx:fallback>
      {{#if _result}}
        <h2>{{_result.count}}</h2>
      {{/if}}
</knotx-snippet>

I decided I would rather not process the internals of the snippet thou.

Additional context Once the fallback feature is implemented in the knotx core we would like to take advantage of it in the data-bridge module.

e: edited to rename the fallback attribute to just "data-knotx-fallback"

marcinczeczko commented 5 years ago

@scypio thanks for the ticket. Looks great. I'd add following statements/design decisions regarding implementation:

Each Knot that processes Fragments:

  1. should have an ability to flag the eligible fragment as failed in case any failure happened during processing of one fragment
  2. should ignore incoming fragments that were flagged as failed by other Knots processed it before
  3. should at least log to the logger clear error message saying that snippet processing failed for a given page URL, in the given Knot, and/or corresponding stack-trace if it was exception
  4. processing of single fragment should have a common place/API so that any custom developed Knot could easily follow it when implementing its own error handling
marcinczeczko commented 5 years ago

@scypio Additionally:

  1. Splitter should extract any fallbacks snippet contains and store it on the Fragment
  2. Assembler is to consume failure flag on the given fragment, and if it's failed follows the strategy for the given fragment: e.g. ignore appending fragment output to the client response (empty strategy), or appends the static failure (that's already in the Fragment, thanks to the splitter)
malaskowski commented 5 years ago

@scypio, @marcinczeczko few thoughts/designs from me:

I like very much the idea of having a place in markup to define the fallback. I agree that this should be later consumed as a separate Fragment by Knots and that it can be reused among other snippet fragments as the fallback content. The trick here is to introduce a new fragment type in a smart way, that will enable further extensions to be added to the Knot.x easily in the future. What we currently have, is FragmentSplitter implementation that distinguish Knot.x fragments and raw HTML fragments. I suggest keepieng to this simple idea and extend the mechanics that enrich Fragment data by its type using e.g. ServiceLoader. This would look like this:

   type = "snippet";
   attributes.put("knots", new JsonArray(knots));
  1. FallbackFragmentFactory can do something like
    type = "fallback";
    attributes.put("fallbackId", fallbackId)
pun-ky commented 5 years ago

fallback strategies looks nice but still extending snippets based on markup syntax is not a way to go in my opinion / not enough universal, see https://github.com/Cognifide/knotx/issues/462#issuecomment-428934565

how about

{{!--#knotxSnippet:productsServerSide(knots="databridge,handlebars", databridge-name="someSearch")--}}`
(ok)
{{!--!knotxSnippet:productsServerSide--}}
(fallback)
{{!--/knotxSnippet:productsServerSide--}}

based on {{#each ...}}...{{else}}...{{/each}}

<script type="application/json">
{{!--#knotxSnippet:productsServerSide(knots="databridge,handlebars", databridge-name="someSearch")--}}`
{{{_response}}}
{{!--!knotxSnippet:productsServerSide--}}
{
    "results": [],
    "numFound": 0
}
{{!--/knotxSnippet:productsServerSide--}}
</script>
malaskowski commented 5 years ago

Fallback is totally re-worked in Knot.x 2. Please see https://github.com/Knotx/knotx-fragments-handler/blob/master/core/README.md for more details