< PreviousChapter 8: Example2408.3.28 Back End: Application Configuration (be/app.ini)The following is the Back-End application configuration in Windows INI format. It specifies the defaults for the command-line options.8.3.29 Back End: Main Procedure (be/src/main.js)The following is the Back-End main procedure in ECMAScript 2015 format. It up-grades the Node environment to ECMAScript 2018 and instantiates the Microkernel framework for loading some standard modules and the custom modules for the GraphQL API and the database access. "start": "nodemon --ext .js --watch . --legacy-watch --ignore node_modules --delay 2 -- src/main.js", "clean": "shx rm -f unp.db unp.log unp.pid", "distclean": "shx rm -rf unp.db unp.log unp.pid node_modules" } }[default] host = 0.0.0.0 port = 9090 daemon = false daemon-pidfile = unp.pid console = true logfile = unp.log loglevel = debug secret = unp db-dialect = sqlite db-database = unp.db db-schema-drop = true frontend = ../fe/dstrequire("babel-register")({ extensions: [ ".js" ], ignore: /node_modules/, presets: [ "env" ] }) require("babel-polyfill") /* main procedure */ require("co")(function * () { const path = require("path") const Microkernel = require("microkernel") /* instantiate a microkernel */ const kernel = new Microkernel() /* define state transitions */ kernel.transitions([ { state: "dead", enter: null, leave: null }, { state: "booted", enter: "boot", leave: "shutdown" }, { state: "latched", enter: "latch", leave: "unlatch" }, { state: "configured", enter: "configure", leave: "reset" }, { state: "prepared", enter: "prepare", leave: "release" }, User Interface Implementation2418.3.30 Back End: GraphQL Network API (be/src/api.js)This is the GraphQL network Application Programming Interface (API) in ECMAScript 2018 format. It configures the GraphQL framework and the GraphQL to SQL bridge. { state: "started", enter: "start", leave: "stop" } ]) /* define module groups */ kernel.groups([ "BOOT", "BASE", "RESOURCE", "SERVICE", "APP" ]) /* load modules into microkernel */ kernel.load( "microkernel-mod-ctx", [ "microkernel-mod-options", { inifile: "app.ini" } ], "microkernel-mod-logger", "microkernel-mod-daemon", "microkernel-mod-title", "microkernel-mod-shutdown", "microkernel-mod-sequelize", "microkernel-mod-graphqlio", path.join(__dirname, "api.js"), path.join(__dirname, "db.js") ) /* startup microkernel and its modules */ yield kernel.state("started").then(function onSuccess () { kernel.publish("app:start:success") }).catch(function onError (err) { kernel.publish("app:start:error", err) throw err }) }).catch(function (err) { console.log("ERROR: failed to start: " + err + "\n" + err.stack) })import GTS from "graphql-tools-sequelize" export default class { get module () { return { name: "api", group: "APP", after: "db" } } latch (mk) { /* provide "frontend" configuration option */ mk.latch("options:options", (options) => { options.push({ names: [ "frontend" ], type: "string", "default": ".", help: "path to frontend data" }) }) } async prepare (mk) { /* establish GraphQL/Sequelize bridge */ let db = mk.rs("db") let gts = new GTS(db, { validator: null, authorizer: null, tracer: async (record, ctx) => { if (ctx.scope !== null) ctx.scope.record(record) }, Chapter 8: Example242 fts: { "Unit": [ "name" ], "Person": [ "name" ] } }) mk.rs("gts", gts) await gts.boot() /* hook into GraphQL-IO for dynamic configuration */ mk.rs("graphqlio").at("server-configure", (options) => { options.set({ prefix: "UnP-", name: "UnP", frontend: mk.rs("options:options").frontend, secret: mk.rs("options:options").secret, graphiql: true, debug: 9, example: "query {\n" + " Units {\n" + " abbreviation name\n" + " director { initials name role }\n" + " members { initials name role }\n" + " parentUnit { abbreviation name }\n"+ " }\n" + "}\n" }) }) /* hook into GraphQL-IO for providing transaction */ mk.rs("graphqlio").at("graphql-transaction", (ctx) => { return (cb) => { /* wrap GraphQL operation into a database transaction */ return db.transaction({ autocommit: false, deferrable: true, type: db.Transaction.TYPES.DEFERRED, isolationLevel: db.Transaction.ISOLATION_LEVELS.SERIALIZABLE }, (tx) => cb(tx)) } }) /* provide GraphQL schema definition */ mk.rs("graphqlio").at("graphql-schema", () => ` type Root { ${gts.entityQuerySchema("Root", "", "Unit")} ${gts.entityQuerySchema("Root", "", "Unit*")} ${gts.entityQuerySchema("Root", "", "Person")} ${gts.entityQuerySchema("Root", "", "Person*")} } type Unit { id: UUID! name: String abbreviation: String ${gts.entityQuerySchema ("Unit", "director", "Person")} ${gts.entityQuerySchema ("Unit", "members", "Person*")} ${gts.entityQuerySchema ("Unit", "parentUnit", "Unit")} ${gts.entityCreateSchema("Unit")} ${gts.entityCloneSchema ("Unit")} ${gts.entityUpdateSchema("Unit")} ${gts.entityDeleteSchema("Unit")} } type Person { id: UUID! User Interface Implementation2438.3.31 Back End: Database Access (be/src/db.js)The following is the code for defining the database schema and pre-filling the da-tabase with initial example data. It is in ECMAScript 2018 format. name: String initials: String role: String ${gts.entityQuerySchema ("Person", "belongsTo", "Unit")} ${gts.entityQuerySchema ("Person", "supervisor", "Person")} ${gts.entityCreateSchema("Person")} ${gts.entityCloneSchema ("Person")} ${gts.entityUpdateSchema("Person")} ${gts.entityDeleteSchema("Person")} } `) /* provide GraphQL schema resolver */ mk.rs("graphqlio").at("graphql-resolver", () => ({ Root: { Unit: gts.entityQueryResolver ("Root", "", "Unit"), Units: gts.entityQueryResolver ("Root", "", "Unit*"), Person: gts.entityQueryResolver ("Root", "", "Person"), Persons: gts.entityQueryResolver ("Root", "", "Person*") }, Unit: { director: gts.entityQueryResolver ("Unit", "director", "Person"), members: gts.entityQueryResolver ("Unit", "members", "Person*"), parentUnit: gts.entityQueryResolver ("Unit", "parentUnit", "Unit"), create: gts.entityCreateResolver ("Unit"), clone: gts.entityCloneResolver ("Unit"), update: gts.entityUpdateResolver ("Unit"), delete: gts.entityDeleteResolver ("Unit") }, Person: { belongsTo: gts.entityQueryResolver ("Person", "belongsTo", "Unit"), supervisor: gts.entityQueryResolver ("Person", "supervisor", "Person"), create: gts.entityCreateResolver ("Person"), clone: gts.entityCloneResolver ("Person"), update: gts.entityUpdateResolver ("Person"), delete: gts.entityDeleteResolver ("Person") } })) } }import Sequelize from "sequelize" import UUID from "pure-uuid" import Chance from "chance" export default class { get module () { return { name: "db", group: "APP" } } latch (mk) { mk.latch("sequelize:ddl", async (db, dm) => { Chapter 8: Example244 /* define data model */ dm.Unit = db.define("Unit", { id: { type: Sequelize.STRING(36), primaryKey: true }, name: { type: Sequelize.STRING(100), allowNull: true }, abbreviation: { type: Sequelize.STRING(10), allowNull: true } }) dm.Person = db.define("Person", { id: { type: Sequelize.STRING(36), primaryKey: true }, name: { type: Sequelize.STRING(100), allowNull: true }, initials: { type: Sequelize.STRING(10), allowNull: true }, role: { type: Sequelize.STRING(100), allowNull: true } }) dm.Unit .belongsTo(dm.Unit, { as: "parentUnit", foreignKey: "parentUnitId" }) dm.Unit .hasMany (dm.Person, { as: "members", foreignKey: "unitId" }) dm.Unit .hasOne (dm.Person, { as: "director", foreignKey: "directorId" }) dm.Person.belongsTo(dm.Person, { as: "supervisor", foreignKey: "personId" }) dm.Person.belongsTo(dm.Unit, { as: "belongsTo", foreignKey: "unitId" }) /* synchronize data model with underlying RDBMS */ await db.sync({ force: true }) /* fill data model with some initial data records (all fully deterministically generated) */ [...] await unit("EC", "Example Corporation") await unit("MA", "Management & Administration", units.EC) await unit("EP", "Engineering & Production", units.EC) await unit("RD", "Research & Development", units.EC) await unit("RDT", "Research & Development: Training", units.RD) await unit("RDI", "Research & Development: Innovation", units.RD) await unit("RDE", "Research & Development: Elaboration", units.RD) await unit("RDP", "Research & Development: Publication", units.RD) await person("BB", "Berry Boss", "Director", units.EC) units.EC.setDirector(persons.BB) [...] }) } }User Interface Results2458.4 User Interface ResultsOur problems are mostly behind us. What we have to do now is fight the solutions. — Alan P. StultsIn this section, the resulting Units and Persons (UnP) application is shown: summary of the source code, screen-shots of the resulting User Interface (UI) and the Com-ponent Tree Visualization of the UI under run time.8.4.1 Source-Code Quantitative SummaryThe entire source code of the Units and Persons (UnP) example application consists of the 31 previously listed source files and 1,767 Lines of Code (LoC) in total, of 25 source files and 1,414 LoC at the Front End, and 5 source files and 321 LoC at the Back End:$ cloc package.json fe be 31 text files. 31 unique files. 0 file ignored. Language files blank comment code ---------------------------------------------- JavaScript 11 105 106 871 CSS 5 33 2 380 HTML 4 18 17 288 JSON 3 0 0 122 YAML 7 17 12 93 INI 1 2 0 13 ---------------------------------------------- SUM: 31 175 137 1767 $ cloc fe Language files blank comment code ---------------------------------------------- JavaScript 8 86 89 611 CSS 5 33 2 380 HTML 4 18 17 288 YAML 7 17 12 93 JSON 1 0 0 42 ---------------------------------------------- SUM: 25 154 120 1414 $ cloc be Language files blank comment code ---------------------------------------------- JavaScript 3 19 17 260 JSON 1 0 0 48 INI 1 2 0 13 ---------------------------------------------- SUM: 5 21 17 321Chapter 8: Example246Figure 8.7: UnP Development-Time Continuous Build-ProcessUser Interface Results247As an example application resembling part of a full-featured Business Information System, UnP is much larger than the usual Hello World, of course. However, it is still small enough to be relatively easy to comprehend and still showcase most aspects of the HUICA. 8.4.2 Development-Time Screen-ShotUnder development-time, the GemstoneJS-based build process is running in a dual-screen setup (see Figure 8.7 on page 246). At the top, the Front-End build process is watching for file changes to its source files and on each file change, it re-packages the Front-End code. At the bottom, the Back End is running in development mode where the Back End is executing and in parallel watching for file changes to its source files. On each source-file change, the Back End is restarting.8.4.3 User Interface Screen-ShotsThe User Interface (UI) of the Units and Persons (UnP) example applica-tion consists of just a single main window with its four Dialogs. In Figure 8.8 on page 248, one can see the UI directly after startup in both the English and German language. The language can be toggled at any time at the top-right corner of the window. In Figure 8.9 on page 249, one can see the UI once after selecting a Unit or Person entity, and once after filtering some Unit and Person entities. In Figure 8.10 on page 250, one can see the UI running twice in parallel (even in two different browsers), allowing two users to edit entities in parallel.8.4.4 Component Tree VisualizationDuring run time, the Units and Persons (UnP) example application consists of 16 Components in total (including the implicit root Component). See Figure 8.11 on page 251 for a screen-shot of the Component Tree Visualization.The UnP Application can switch the language on-the-fly, uses master-detail communication and sup-ports real-time updates for all parallel connected front-ends.Chapter 8: Example248Figure 8.8: UnP Screen-Shot after Startup (English and German Language)User Interface Results249Figure 8.9: UnP Screen-Shot after selecting and filtering Units and PersonsNext >