惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 最新话题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Forbes - Security
Forbes - Security
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
W
WeLiveSecurity
Jina AI
Jina AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
Engineering at Meta
Engineering at Meta
PCI Perspectives
PCI Perspectives
Martin Fowler
Martin Fowler
T
The Exploit Database - CXSecurity.com
F
Full Disclosure
WordPress大学
WordPress大学
S
Security Affairs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
SegmentFault 最新的问题
P
Privacy International News Feed
IT之家
IT之家
M
MIT News - Artificial intelligence
G
GRAHAM CLULEY
Hacker News: Ask HN
Hacker News: Ask HN
D
DataBreaches.Net
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
Check Point Blog
美团技术团队
Security Latest
Security Latest
Cyberwarzone
Cyberwarzone
N
News and Events Feed by Topic
MyScale Blog
MyScale Blog
H
Help Net Security
宝玉的分享
宝玉的分享
The Hacker News
The Hacker News
The Last Watchdog
The Last Watchdog
The Cloudflare Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
I
Intezer
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
AI
AI
I
InfoQ
N
News | PayPal Newsroom
TaoSecurity Blog
TaoSecurity Blog

ValeriaVG

Agentic Development Survival Guide Simplicity Police The sunset of Arrogance as a Service How do I delegate when I can do it faster myself? Implementing custom Calendar component ES Modules & Import Maps: Back to the Future Me & React: 5 Years in 15 Minutes 10 Reasons NOT to Use Go for Your Next Project How Build a Web App in 11 Minutes and Fall in Love With Sveltekit Master Git in 7 Minutes Crc 32 Checksum in Wasm and Raw Js Tutorial and Benchmark Master Binary in Five Minutes Five Pro Tips to Master Promises in Js How to Use Custom Files as Modules in Nodejs How to Do Magic With Numbers 5 Ways to Use Redis in Your Next Project
Plug & Play Modular Architecture for Scalable and Maintainable Apps
Valeria Viana Gusmao · 2023-06-28 · via ValeriaVG

A new project is always a joy: the code hasn't yet been bloated with deprecated features, rushed decisions and questionable dependencies. When my projects get to this state I become increasingly less productive and the urge to refactor grows exponentially, which, as you would imagine, halts any progress entirely.

After a bit of trial and error I developed some rules that help me keep the code maintainable and scalable for longer and I'm excited to dive into it as promised.

General Idea and Principles

I needed a structure that would free me from distractions as much as possible and would allow me to concentrate on task at hand. At the same time it should be possible to make changes of any magnitude confidently. And, finally, it needed to be simple and easy to replicate on different types of projects, regardless of it being a website, a mobile app or a distributed backend system as I was tinkering with them all.

It might seem like a lot of requirements, but it all boils down to having a proper and very limited scope for features. While looking for ideas I stumbled upon an article (that I sadly cannot find anymore, but would love to give credit) that described modular architecture for a NodeJS app and it inspired me greatly: the core principle was that the code that belongs together should stay together and that the structure should allow easy addition and removals of features as a whole.

With time I settled for the following rules when structuring an app:

  1. Each separate feature should fit entirely in it's own folder.

  2. Only imports from shared folders or within current feature folder are allowed.

  3. Each feature can be written and structured differently from the others, as long as it has the same "public" interface.

  4. Features can be composed of nested features, but it shouldn't be more than two levels deep (up to a grandparent).

  5. Code structure should reflect how code is deployed or packaged, if applicable.

For visual reference imagine a communal house with three roommates: Top block: Shared Space:Kitchen, Bathrooms, Living Room; Three rooms below, labeled "Room 1", "Room 2" and "Room 3". Room 1 has a "Bed" and a "Drawer", Room 2 has a "Bed" and "Walk-in closet" that in it's turn has "Shoes", "Dresses", and "Lingerie". Room 3 has nothing but a "Mattress"

Each one of them can use shared space, provided they'd keep it tidy and don't leave anything that's not necessary there. Roommates would not allow other roommates take things from their rooms; and anyone of them might move out at any given time and it shouldn't affect the others. At the same time every roommate should be free to organise their space to their liking and do whatever they please there.

As you can see this structure works perfectly for dorm living arrangements ;-) Let's see how it might look like for actual apps. I'll use JavaScript and some popular frameworks, but it should be possible to apply this paradigm for any other language or tool as well.

React Frontend App: Dashboard

Say you're working on an internal tool to manage some eCommerce website. You'd probably need the following features:

  • Ability to authenticate users
  • Ability to manage blog posts
  • Ability to see and manage the shop

I created React App with:

npm create vite@latest

And outlined the basic folder structure: New folders within "src": "context","components" and "modules". Modules have "authentication","blog" and "shop"

Each module will have it's own set of dynamic and static pages and, probably, menu elements. Let's add a router:

npm i react-router-dom --save-dev

And define the interface for the modules in src/types.d.ts:

import { ReactComponentElement } from "react"; import { RouteObject } from "react-router-dom"; declare global { type AppMenu = { title: string; }; type AppModule = { routes: Array<RouteObject & { menu?: AppMenu }>; wrapper?: (node: ReactNode, modules: AppModule[]) => ReactNode }; }

The gist of each module is a set of routes, with the addition of a wrapper to allow access to other modules, e.g. for the menu, as well as allow different layouts, e.g. for login page.

And example module would look like this:

import { wrapper } from "../../components/Layout"; import CategoriesPage from "./categories/CategoriesPage"; import OrdersPage from "./orders/CategoriesPage"; const shopModule: AppModule = { routes: [ { path: "/categories", element: <CategoriesPage />, menu: { title: "Category" }, }, { path: "/products", element: <OrdersPage />, menu: { title: "products" } }, { path: "/orders", element: <OrdersPage />, menu: { title: "orders" } }, ], wrapper, }; export default shopModule;

Then the code to connect multiple modules together could look like this:

import { RouteObject, RouterProvider, createBrowserRouter, } from "react-router-dom"; import modules from "./modules"; import { ReactNode } from "react"; const wrapWithLayout = ( routes: RouteObject[], wrapper?: (node: ReactNode, modules: AppModule[]) => ReactNode ): RouteObject[] => { return routes.map((route) => ({ ...route, element: wrapper ? wrapper(route.element, modules) : route.element, })); }; const router = createBrowserRouter( modules.reduce( (a, c) => [...a, ...wrapWithLayout(c.routes, c.wrapper)], [] as RouteObject[] ) ); export default function App() { return <RouterProvider router={router} />; }

You can check full implementation and try it yourself in this example repo.

NodeJS API with Express

For this example let's bootstrap it with express.js:

npm init -y && npm install express --save && npm install @types/express typescript ts-node-dev --save-dev

Add tsconfig.json:

{ "compilerOptions": { "module": "commonjs", "moduleResolution": "classic", "esModuleInterop": true }, "include": ["src"], "ts-node": { "files": true }, "files": [ "src/types.d.ts" ], }

And, once again, let's create basic interface for the modules:

//src/types.d.ts import { Express } from "express"; declare global { type APIModule = { middleware: Array<(app: Express) => void>; }; }

An example module could look like this:

const helloModule: APIModule = { middleware: (app) => { app.get("/hello/:name", (req, res) => { res.send({ hello: req.params.name }); }); }, }; export default helloModule

And the glue to make all the modules and app as a whole work together is as simple as:

import Express from "express"; import healthModule from "./health/index"; import helloModule from "./hello/index"; const modules = [healthModule, helloModule] const app = Express(); modules.forEach(mod=>mod.middleware(app)) app.listen(3000, "localhost", () => { console.log("Listening on http://localhost:3000"); });

You can explore the full example in this repo.

With middleware one can overload context and handle bunch of complex use cases, including authentication, but most importantly this kind of structure allows almost seamless migration to microservices architecture!

Not a fan of Express or middleware? Check out my work-in-progress Deno headless CMS implementation that is built using the same principles.

Those examples are relatively simple, but the only difference between these projects and bigger systems are amount of folders within modules! And those of you who enjoy freelancing or consulting could appreciate building a library of "modules" to reuse, I sure did ;-)

So, what do you think? Ready to give it a try? I'd love to see what implementations you come up with!