Nebulous Puffy Marshmallows

    @focuson/view-components
    TypeScript icon, indicating that this package has built-in type declarations

    0.2.31 • Public • Published

    Warning: this is still R&D

    The ideas here are still under development.

    The problem that is being solved

    You can read here more details about the problem being solved, and how this compares to traditional solutions. The 'tl;dr' version is that modern guis and apis often break. They are typically tightly coupled to each other and a change anywhere in that distributed monolith has unpleasant consequences varying from 'outages' to 'loss of features'. The symptoms that you have a distributed monolith instead of a system designed to last for decades is having schemas (i.e. out of band communication) between the apis and between the api and gui.

    This approach allows the apis to change, allows multiple versions of the api and backends to exist with radically different data representations and features (for example to support localisation or experiments) and the front end keeps on working with the need for complicated code changes, feature flags, redeployments and other complexity

    The solution is simple, easy to use and works

    The solution

    You can read here how the solution actually solves the problems of geographic diversity, breaks the distributed monolith, and delivers a decoupled system that is designed to last for decades. The 'tl;dr' version is that

    • Each server version knows the data that it is delivering
    • The server maintains small sub components for the volatile and varying parts of the code
    • When the server delivers the data includes information for the client to know how to render it

    This means that we don't need to worry about schemas, we don't need to worry about versioning schemas and testing that the new code works with old versions. For both the client and the server there is only 'now'. The code and the data that is being served by the server

    What does this mean for testing

    The author of this toolkit has spent years working with contract testing. One of the most exciting things about the approach in this project is that it pushs tests that would normally have to be done as integration or contract tests further down the testing pyramid. The sames tests need to be written, but they now run faster, are easier to manage, and because they only have to test 'now' and not 'all the previous versions' they are much simpler to write and maintain

    Using the code

    Toolchain

    Currently the toolchain is extremely primitive. I use two command terminals: One to compile the code on demand, and one to run the react code. It would be nice to incorporate the code on demand code into the tool chain and that is 'on the backlog'.

    Command line

    There is a command line tool that transpiles components and json @focuson/cod. Typically when working I run this tool in a different terminal than the react server.

    Writing React 'Code on Demand' Components

    Components must be totally isolated: This is of course good practice when using react. The only interaction with external components must be through the context, or the properties. Apart from this requirement, there is almost no difference between a standard react component and a 'code on demand' component.

    Using the components

    The essential difference between a normal gui and using code on demand is that the data comes from the server, and so does the component for serving the data. In the data the server 'tells' the client which component to use and that is loaded.

    Here we see a typical 'main method'

    function loadJson(url: string) {
        const domain: GameDomain = {loadJson, onClickSquare}
        return loadJsonFromUrl<GameData>('game', cache, (cache, s) =>
            ReactDOM.render(
                <ComponentCacheContext.Provider value={cache}>
                    <GameContext.Provider value={domain}>
                        <ComponentFromServer state={s}/>
                    </GameContext.Provider></ComponentCacheContext.Provider>, element))(url)
    }
    
    loadJson('https://example.com/game')

    This shows most of the salient features. The external work is injected through the GameDomain. There is a 'cache' which looks after loading the components and then we load the component from the server.

    Behind the scenes the json is loaded, in it the '_render' field is found and triggers a load of that component (the cache ensures that this is only loaded once) That component is rendered

    But how do I do state management and side effects and things???

    The example projects show some simple examples. In the 'Configure Price Quoting' example The use of the @focuson/state allows state management to be composed and majority of it is baked into the component. When it is better to have a side effect such as 'you win' or 'pay', then contexts can be used to inject functions that can be called.

    The resulting code is typically beautifully decoupled (constraints empower, and being forced to only use Context or Props) makes a big difference.

    Rendering child components

    Clearly we want to split components up. Typically large components are made of smaller components that each render and manage state for part of the main state tree. These are typically loaded from the server using the <ComponentFromServer> or <ChildFromServer> (a similar tag: see below). The only communication is through the lens state or a context.

    How does this relate to Rest

    In order to be restful we must follow the key guidelines. The primary one is 'no coupling between client and server'. The recommended approach is to use a media type. There is an excellant summary here https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven.

    Here we are effectively a media type. We imagine the json is just like a pdf or other media type. The data itself details enough information to know how to render it. We slightly violate the rules of rest if we use contexts, but pragmatically speaking they can make things easier, and as long as we design them careful they effectively become part of our media type.

    Extended Language of our media type

    Our media type is json with the following extra.

    • _render should have a _self containing the code for rendering a class. Can have other properites for dependent components
      • <ComponentFromServer> uses the _render/self property
    • _render can over other properties

    Example json

    {
      "_links"   : {
        "game1": {"href": "created/gameJson1.json"},
        "game2": {"href": "created/gameJson2.json"}
      },
      "_render"  : {"_self": "created/Game/d56195b3feeea272a3811e91a3c9cb195f263702ba6f631690afd5049bc2b38f"},
      "state"    : "X",
      "_embedded": {
        "board": {
          "_render": {
            "_self" : "created/Board/e4217d198b2b8cfd87588a89b113a557a0271f757167fa0453926fe79974e7ff",
            "square": "created/Square/73d34b45dc191f01cf003e8e91a869d2d4fc17038fd259baf6458c94c629b044"
          },
          "squares": ["", "", "", "", "", "", "", "", ""]
        }
      }
    }
    • The main _render is for the 'Game' component which can be found here
    • The _render inside the _embedded.board render the board and can be found here
    • The square inside the _embedded.board._render can be used by any <ChildFromServer> component, and in this case is used to render the squares

    The long string of hex digits in the _render are the sha of the text of the component, and if it doesn't match the component will fail to load. This prevents man in the middle attacks injecting arbitary code into the component.

    How do I create the urls like created/Game/d561...

    There is a tool cod (stands for 'code on demand') which can be installed with

    npm install -g @focus/code

    The main command (which is normally executed in the same folder as public) is cod build. To see the options type cod build --help.

    This tool will

    • take typescript code from a src/render subdirectory, transpile it and find the sha putting it into public/created/ComponentName
    • take json from a src/json subdirectory and modify it to use the generate shas putting it into public/created/json There are configuration options to override the defaults.

    Creating json for tests

    In src/json we can put json. The example above looks like this

    {
      "_links"   : {
        "game1": {"href": "created/gameJson1.json"},
        "game2": {"href": "created/gameJson2.json"}
      },
      "_render"  : {"_self": "#Game/render#"},
      "state"    : "X",
      "_embedded": {
        "board": {
          "_render": {
            "_self" : "#Board/render#",
            "square": "#Square2/render#"
          },
          "squares": ["", "", "", "", "", "", "", "", ""]
        }
      }
    }

    After running cod build we can use the json in tests, or display it directly in the gui

    Creating json using the server

    At the moment there aren't any tools that do this automatically. The code in cod is exactly the code that needs to be used, but it hasn't been extracted into a suitable library yet. All we need is json code that represents a function, and it to have the correct filename which includes the filename. These tools are on the backlog and will be added soon

    Install

    npm i @focuson/view-components

    DownloadsWeekly Downloads

    1

    Version

    0.2.31

    License

    MIT

    Unpacked Size

    13.4 kB

    Total Files

    9

    Last publish

    Collaborators

    • phil-rice