A collection of articles, apps, and other digital resources, thematically tied to the subjects of art, design, programming and general philosophy. All content created by "The Imp".

This website does not use cookies, because they are high in sugar and saturated fat. (Yes, they are tasty too, I know, I know...)

A highly polymorphic architecture

26/04/2019

Towards the end of 2019, I developed a desire to father a few online content outlets. I am a pretty prolific dude - always working on a handful of (what I deem to be, at least) exciting schemes, designs and projects. As a self-professed INTJ and Arts graduate, it is simply in my nature. I find it immensely fulfilling creating things, and will be abundantly motivated to do this regardless of any external factors. However, keeping all of my work private, regardless of its public worth or relative inconsequentiality, created a sense of inefficiency and incompleteness.

I decided then, to create two websites to document some of my output: socket-one.com for right-brained things, and socket-two.com for left-brained things.

Socket one would feature more traditionally creative work such as illustrations, videos and recorded music, and also things that are irreverent, experimental or unfinished. Socket-two.com would primarily feature writing and programming, and work that is generally more refined and austere.

At the time that I was considering this, I also maintained a website that promoted my services as a freelance Web Developer, which consisted largely of a portfolio gallery. I decided that this could also benefit from a (re)implementation.

Fleshing out the requirements

My basic requirement was therefore in the shape of three small, serialised-content-driven websites. What was interesting about this requirement - aside from the obvious symmetry in technical scope - was the small overlap in purpose: While most records would quite clearly belong to one site or another, it was foreseeable that every so often, a particular project would logically belong to several.

(A note to any search engine optimisation enthusiasts that may be cringing at this notion: Yes, featuring the same material in a different location can be slightly bad for Google page rankings, but Google page rankings are only of absolute importance if you are running a content farm. *shivers*).

While I wanted to be able to vend like content in multiple locations, I wanted to avoid having content that was duplicated in the sense of duplicated data; existing as several identical files or database records.

I also wanted to avoid the tedious and unnecessary task of creating three separate systems, when each did virtually the same thing. I decided that it would also be undesirable to have three pasted copies of the same server side code floating around on the server filesystem.

My solution was a little bit unconventional, which is why I thought it might be interesting enough to share.

I conspired to create a cluster of code bubbles. This structure could be described more specifically either as multiple web apps sharing the same core, or as a single web app that had many faces. There would be a single core server side framework, and a single content database and media box for centralised image hosting. This approach is not unheard of. But slightly more unusually, I also decided that I would use a common, shared javascript framework, and author common, shared frontend web components and views. The SCSS "framework" of helper functions, mixins, and core variables would also be shared.

In other words, the solution would strive for a complete elimination of duplicated code - and content sources - of any kind, among a cluster of similar websites.

The worry that the efficiency of so much shared resource would be trumped by an insufficient freedom to diverge among web applications caused me to double check my design. However, I concluded that the similarity of purpose, and a resolve for careful, judicious separation of base entities from app-level extensions validated the architecture.

Having previously found browser-based WYSIWYG editors to be less convenient than conventional text editors, I also had a desire for a system that leveraged files as the vehicle for content-to-database shuttling. I also wanted to be able to choose between writing the textual portion of a piece of content in either markdown format or HTML, as dictated by the nature of each publication.

I would also seek to keep the final solution as lean as possible, avoiding the use of any unnecessary build tools, package managers, or third party frameworks. Like any other developer in his or her right mind, I do encourage the use of build tools, package managers and third party frameworks, where such tools are appropriate. However, there is a recent trend for using them gratuitously, in situations whereby they do not bring a benefit. I will always advocate a use of such things that is characteristically Spartan - but not severe.

Macroscopic solution architecture

The diagram below is rudimentary, but it should loosely illustrate how the architectural aspects all fit together. As the websites I want to host are small, and each should not need to process a great deal of traffic, the whole solution is designed to exist on a single server.

Every aspect of the diagram represents a resource that is shared, except for the area in the bottom right - which depicts individual websites. These are independent of one another. Each website node leverages a large quantity of shared resource, but may, in addition, house it's own stylesheets, template images, idiosyncratic web components, and serverside classes.

Figure 1: "Whiteboard diagram" showing key architectural concept. Click to enlarge.

The website nodes shown in the bottom right portion of the diagram all have an index.php entry point, which is represented in the diagram by EP). A central feature of the architectural concept is that many domain names will feed into the same application. An Apache webserver config file featuring VirtualHost entries is responsible for setting up the domain-name to entry point associations, and the code below demonstrates exactly how this is accomplished. (The DocumentRoot has been falsified for security reasons, but is still illustrative).

Apache configuration file (edited for brevity and security)

The last thing to cover is the content authoring process. This is represented by the top third of the diagram. As represented by the bubble on the top right, blocks of website content (articles, images, apps, and so on) exist as directories, and each directory houses textual content (in md or HTML format), content metadata (title, date of creation, a list of websites that the content should appear on), and media files.

To publish content to the website, the content building process, a PHP script, is run from the command line. This iterates through all directories within the content directory, copies media files to the media server, and inserts textual content appearing in HTML or md files, alongside metadata, into a database. Once the content is within the database and the media server, any website that is part of the cluster may access it (provided the metadata of the content in question permits it), via the serverside framework.

I should mention that I am writing in generic terms when I refer to a "media server". In the context of this solution, the "media server" is actually just a directory on the main server which has it's own subdomain. However, the solution could be reconfigured to use a media server such as Amazon S3. All images encountered by the media building process will be copied to this location at their original resolution, but also as smaller thumbnail versions for use in content lists.

A more detailed depiction of the solution architecture

The diagram below shows the whole solution, a bit more lucidly, divided into four parts. The common code powers each of the apps, which display the decentralised content.

The build code implements a variety of automated tasks, including the establishing of the database structure, the seeding of the database with test records, the compilation of frontend assets, and the publishing of content by means of the processes described above. All build processes - including PHP based build processes - are grouped together and executable by means of an all-encompassing NodeJS script.

Figure 2: Whole solution architecture diagram. Click to enlarge.

It's worth clarifying that the Media box and App box entities within the Apps quadrant are not fully-fledged apps - they do not leverage the custom severside MVC framework. Instead, they are both just directories that hold files which can be served directly to the browser. The app box, which has not yet been explained, houses independent applications for demonstration purposes. Once established in this location, each demo application can be referenced by content records of the app type, and subsequently linked to or embedded.

The text on the right depicts the (collapsed) file structure of the whole solution. This is shown to convey how the conceptualisation of the solution as content, apps, build and common areas has translated directly into the project's filesystem structure. build.js is the NodeJS entry point into all the afore mentioned build tasks. The config.php contains configuration values that are pertinent to the system as a whole - as opposed to configuration for individual websites. It contains database credentials, and an app mode flag (i.e. dev / prod).

Solution filestructure

The custom server side framework

In consideration of a small website sat upon a LAMP stack, a simple, bespoke PHP MVC framework can be the perfect fit. It is common for a developer to habitually reach for a feature-rich third party solution, when it is not the optimal solution. In the case of this project, the list of wants was small, comprising an MVC foundation and a simple MySQL access layer. This, and the need to be able to elegantly join the said features onto some slightly unconventional code sharing structures, qualified a custom framework approach as the most apposite option.

I elected to use a minimalistic PHP MVC framework that I had created a few years prior, in 2014, as the basis for the new, bespoke foundations. However, reading through the code, I was dissatisfied with it, so I carried out a complete rewrite. The jocular namesake from the first iteration persisted though - the "Doge framework".

Designed for use in the context of small websites, and intended to speed up development time, the Doge framework was built around another fairly novel architectural concept: No routes, and optional controllers.

In order to create a page in a web app that leverages the Doge framework, all that is required is a view file. When the user hits the application entry point, the framework examines the request method and the URL. It then attempts to infer a view file path, and a controller name and method based on these variables. If a view file exists at the deduced file path, the HTML contents of the view in question is wrapped within the HTML of the template view file, and transmitted to the browser. If a corresponding controller method also exists, the controller method is called prior to rendering the view (and this is often done as a means of assembling computed data for use in the latter).

This mechanism demands that there are direct correlations between controller names, URLs, and view names. Placing such a demand on naming schemes could be frustrating in some projects (particularly larger ones), but in the context of microsites, it can be a helpful convenience.

The following diagram depicts the new, derived PHP framework structure. It is quite minimalistic, with the App Core file (actually called Doge in the source code) and the RootController (which all other application-specific controllers extend from) taking responsibility for all the route handling and related functionality that is described above.

Figure 4: Server side framework diagram. Click to enlarge.

The SiteConfig and Input entities are simply utility classes. Input provides a facade for easily accessing and filtering data present in PHP's $_GET and $_POST superglobals. SiteConfig is an object representation of the configuration file that appears within the root directory of the operative website. It features methods for correctly getting values from this data structure.

Laravel provides a wonderful API, but can be overkill for a small project. However, thanks to the work of rappasoft, its possible to download all of Laravel's excellent helper functions in the form of one portable file. This is fantastically convenient for a simple project such as this, so they were included. An additional helpers file completes the picture.

As one might expect, the boot file simply loads all of these scripts.

Order of events

The following list describes the process that delivers a user with content, by means of the Doge framework.

  1. A user opens their browser, and enters www.socket-two.com in their address bar.
  2. The user's browser is pointed to the socket two server, as per DNS implementation.
  3. The request is handed from TCP/IP to Apache.
  4. Apache's configuration file compels it to redirect all requests that are based on the domain name socket-two to /var/websites/cluster-root/apps/socket-two/public/index.php.
  5. The index.php file loads more scripts, including the framework boot script, which loads scripts of its own. It then creates an instance of the core application class, Doge, to which it passes global config data, and config data specific to the website that is being accessed.
  6. The instance of the Doge framework then sets to work, calling a controller method if applicable. This will typically be to fetch a resource record from the database, and promote the said data for rendering within the view.
  7. Finally, the framework sends a view file, which it interpolates with the site template, and any model data that might have been retrieved. If the request method and URL do not match any corresponding view file or controller method, a 404 response is rendered instead.

The file below shows the entry point for socket-two.com that is hit by Apache. All website faces that are part of the cluster have their own version of this file.

The public entry point for the socket-two.com website

Above: Note how the configuration data for the operative site is passed into the Doge framework object. It is this configuration data that tells the Doge framework instance which universe it is living in, and which website is being requested.

The Doge framework boot file

The architecture of an extending website

Having described the common libraries, the architecture of an extending website is fairly unremarkable, and is as one might expect. Regardless, the diagram below depicts this structure for the sake of completeness. Note that the common Doge framework does not have an extending class for each website. Instead, all websites are implemented by an instance of the Doge base class, and divergences in behaviour are achieved by the following mechanisms:

  • A distinct configuration file for each website.
  • A distinct server side entry point (index.php).
  • Distinct serverside controllers and views.
  • A distinct client side entry point (app.js), into which varying component sets are injected.
  • A distinct SCSS entry point (app.scss), into which varying, idiosyncratic stylesheet sets are injected.

Naturally, an extending website may also load it's own idiosyncratic helper classes when this is desired.

Figure 3: App architecture diagram. Click to enlarge.

To illustrate a website's ability to selectively load javascript components, the entire socket-two javascript entry point is shown below.

Webpack is used to load web components from both common areas and areas that house components specific to the operative website. The componentManager singleton provides a method mountAllComponents to mount all components that are supplied to it by means of a component-listing object.

app.js: Javascript entry point for socket-two.com website

Like the PHP framework, the javascript web component framework used in this project is custom-made. I would not recommend the creation of such a thing as eagerly as I have recommended the creation of a custom PHP MVC framework, as in the case of the latter, more work is involved, and a greater breadth of features needs to be implemented for all basic eventualities to be covered. This is to the extent that leveraging a third party solution, such as React, Angular, or Vue is usually more pragmatic.

The following snippet demonstrates the boilerplate code that is used to describe a single component. Note that it loads the Component prototype, and a few other things, from common areas.

Socket two web component extending base web component

As mentioned above, each website has it's own SCSS entry point. This simply loads the style scripts for all common mixins, variables, and declarations, the required common components, and then finally, idiosyncratic components.

Socket two SCSS entry point file

Deployment

The final piece of the puzzle is a deployment solution. I feel that it is important to be able to deploy a website easily. For a large project practising continuous delivery, it is crucial for keeping customers happy. For me, it helps ensure that a potentially time-consuming process does not make me reluctant to issue new content.

The deployment solution I use is a bespoke NodeJS command line utility, which I designed to supersede my own deployment bash scripts. The ability to represent websites, servers, and disks as objects is a key advantage to implementing a deployment solution in a high-level language. This lends itself well to a much more elegant implementation.

Resting upon this notion, the program can carry out deployments, or bidirectional backups of volumes, websites, and operating systems, in response to a simple command that features a source identifier and a destination identifier.

Conclusion

Having realised the cluster of websites in the intimately intertwined architecture described above, I can happily, honestly, and smugly report that the project was a great success.

  • Circumventing the creation of three systems by creating a single, all-encompassing system did, in practice, reduce the workflow by nearly two thirds.
  • Being able to fix or modify more complex components, such as the comments component, in one place, and have the fix instantly propagate was particularly handy.
  • Having these applications encapsulated within a single system is logical and tidy, considering the similarity of each system's final cause.
  • Having a content-authoring workflow that is common to several locations is a boon, and nullifies the cerebral overhead that swapping between a variety of systems could incur.

No side effects occurred as a result of the intense code-reusage patterns.

The success of this rather incestuous system is highly attributable to it's small size and commonality in website purpose. Using a very similar system in the context of a collection of more disparate websites would be unwise.

Comments (0)

Replying to: Noname

Error

Utilising this page as a billboard for marketing purposes is not allowed. Any messages posted by users that appear to be commercial in nature will be deleted, and any user found breaching this term will have their IP address reported to ICANN. This may result in their networks appearing in worldwide electronic communication blacklists.