Kornea Bauble

Gasket Layer Architectural Pattern

Screen Shot of TypeTango Gaskets Directory Structure
Update: I originally called the facade pattern the gasket pattern. I've put into words one of my main architectural patterns. I'm calling it the gasket layer. It is a custom-written (never a third party) layer of abstraction that sits between application logic and implementation details. Implementation details include how to fetch a row from a database, which arguments to pass to which methods in order to send an email, which cookies to set, unset, and check against the database in order to manipulate the user's login status, etc. Our application logic talks to the gasket layer in whichever words future programmers reading the application logic would find most clear--plain English words with no cryptic identifiers (such as "e" or "fh", which you would never say in English). The gasket layer doesn't care about application logic, it just takes clear out-of-context commands and makes it so by handling the implementation details. This has two virtues: 1. Clarity - Programmers don't get distracted by implementation details while trying to understand application logic. Clarity speeds up comprehension and reduces bugs. This is a sufficient reason to adopt a gasket pattern, and was really my only reason. But as a side-effect, the gasket layer has another major virtue: 2. Compartmentalization - The implementation details can change. For example, perhaps you were first sending email using PHP's native mail() function, then switched to Swift mailer in order to send attachments, then switched to Mail_Mime because Swift started requiring Composer, then implemented sending through Amazon's Simple Email Service because you started using their cloud platform and your emails were going to spam. If application logic concerned itself with implementation details, this would mean a lot of changes to application logic as implementation details change. Similar happened when PHP switched from `mysql_` to `mysqli_`. By having the application logic interact exclusively with the gasket layer (which is easy because you can always add more gaskets), changes in implementation details do not affect the application logic, but only a particular gasket or set of gaskets (changing your database server would only affect the DB class and perhaps a few queries in the model library that do something esoteric in non-universal SQL dialect). ******** Major Gaskets **** Model Library. The biggest and most complicated gasket. Two types of files comprise the Model library: Models and Finders. ** Models. A Model pretty much represents a row somewhere in the database that has a primary ID of its own. $user_id_or_error_messages = UserModel::create($form_data); $user_id = is_numeric($user_id_or_error_messages) ? $user_id_or_error_messages : null; $error_messages = $user_id ? null : $user_id_or_error_messages; if ($error_messages) { $pageShell->error($error_messages); } $userModel = new UserModel($user_id); $username = $userModel->getUsername(); $error_messages = $userModel->setUsername($username); if ($error_messages) { trigger_error("Error setting username to current username: " .print_r($error_messages, true), E_USER_WARNING); // no gasket } $userModel->logIn(); $pageShell->success(); Perhaps a better (PHP7) way to do it: ['user_id' => $user_id, 'error_messages' => $error_messages] = UserModel::create($form_data); if ($error_messages) { $pageShell->error($error_messages); } $userModel = new UserModel($user_id); ** Finders. A Finder is used to search tables and fetch data from potentially multiple rows, although it is often convenient to use it to fetch a single record, because a part of its job is creating ad-hoc fields. An ad-hoc field is one which your application logic needs but which does not literally exist in the database. For example, if you made the mistake of storing the user's name in users.first_name, users.middle_initial, and users.last_name, your application code can tell a Finder that it wants 'full_name'. The Finder's job is to understand 'full_name', and create it on the fly programmatically by combining first_name, middle_initial, and last_name. Since the Finder has to handle these ad-hoc fields anyway, Models delegate some work to Finders even when only one record is being fetched. $user_id = UserFinder::getUserIdFromEmailAddress($email_address); $desired_fields = ['user_id', 'email', 'username', 'date_of_birth', 'city', 'state', 'country', 'gender']; $userFinder = new UserFinder($desired_fields); $userFinder->setPageSize(20); $userFinder->setCurrentPage($current_page); $userFinder->setCompanyId($company_id); $company_users_result = $userFinder->find(); $company_users_array = DB::getTable($company_users_result); $pageShell->success($company_users_array); **** PageShell The second largest gasket is the PageShell gasket. A PageShell represents a type of page. For example, there is typically a different set of headers, footers, navigation menus, etc on the client front-end and the admin back-end, which means that we'd have two gaskets: AdminPageShell and StandardPageShell. They both extend HtmlPageShell, which provides common methods such as ->addJsFile(). HtmlPageShell in turn extends HttpPageShell, which provides common methods such as ->redirect() and ->requireBasicHttpAuth(). AjaxPageShell also extends HttpPageShell. HttpPageShell -> AjaxPageShell HttpPageShell -> HtmlPageShell -> StandardPageShell HttpPageShell -> HtmlPageShell -> AdminPageSHell Each page instantiates one of these page shells or whatever others you add for your system (perhaps you'd benefit from a HomePageShell). In the case of HtmlPageShell and its children, instantiation causes output buffering to start. When the script ends, that PageShell instance's destructor will be called, which echoes content of the output buffer enclosed in whatever HTML markup is appropriate--HtmlPageShell prints the section and echoes the content of the output buffer between the tags. In case of StandardPageShell and AdminPageShell, they rely on HtmlPageShell to enclose their output within tags, while they echo the content of their own output buffers between the appropriate header and the footer. StandardPageShell and AdminPageShell also include any necessary JS and CSS files such as jQuery and `admin.css`. To manipulate the of a page, the application logic calls various HtmlPageShell methods such as addJsFile() addCssFile() addCssCode() addJsCode() setTitle() setDescription() setKeywords() appendToHead(). $pageShell = new StandardPageShell("Test Page"); $pageShell->requireBasicHttpAuth(); // StandardPageShell inherited this method from HttpPageShell $pageShell->addJsFile('/pages/test/test.js'); // StandardPageShell inherited this method from HtmlPageShell $pageShell->addCssFile('/pages/test/test.css'); // StandardPageShell inherited this method from HtmlPageShell $pageShell = new AdminPageShell("Admin Test Page"); // This immediately dies with a HTTP 403 if the user isn't an admin.
Copyright Val Kornea