Gesso: Unify
Unified Plugin-Based System Architecture for Modern Web Applications
Introduction
Modern web applications often need to integrate with multiple backend systems: Content Management Systems (CMS), Enterprise Resource Planning (ERP) systems, eCommerce platforms, and more. As applications grow and evolve, frontend developers require a consistent way to interact with these diverse systems without being bogged down by their complexities. This whitepaper introduces a plugin-based architecture that abstracts the intricacies of these systems, providing a unified approach for frontend developers.
Overview
The proposed architecture uses a plugin-based system that lets backend or API developers create specific plugins for each backend system. Frontend developers can then use these plugins within their components, largely unaware of the specific backend system being interacted with. This modular approach not only promotes separation of concerns but also offers scalability and ease of maintenance.
Core Concepts
- Plugin System: A plugin is a self-contained module that handles interactions with a specific backend system. It provides standardized methods to perform operations like fetching, adding, updating, or deleting data.
- Unified Interface: All plugins adhere to a uniform interface. This means that, regardless of the backend system, the functions and data structures returned by a plugin have a predictable format. This uniformity simplifies frontend development.
- Context Providers: State management, including error handling and data fetching states, is handled by overarching systems. Context providers, like React's Context API, can be used to manage and distribute this state to components that need it.
Advantages
- Flexibility: New backend systems can be integrated with minimal disruption. By creating a new plugin for the system and ensuring it adheres to the established interface, frontend components can immediately start using it.
- Maintainability: The modular nature means that updates or changes to a specific backend system only require changes to its corresponding plugin. The rest of the frontend remains unaffected.
- Scalability: As the application grows, adding new features or integrating with additional backend systems becomes straightforward due to the standardized approach.
- Clear Separation of Concerns: Backend developers can focus on the intricacies of their specific systems and the creation of the plugins. Frontend developers can then build components without needing deep knowledge of the backend implementations.
Implementation Example
In this example, the getProduct
function can be used to fetch product details from a given backend system:
export const getProduct =
(plugin?: ProductPlugin, config: Config = {}): GetProduct =>
async (sku: string) => {
if (!plugin) {
const product =
mockProducts.find((mockProduct: Product) => mockProduct.sku === sku) ||
undefined;
return createResponse(product);
}
let error;
const product = await plugin(config, sku).catch((err) => {
error = err;
return undefined;
});
return createResponse(product, error);
};
Further, useProduct
hooks into getProduct
to provide a React hook version of the same functionality:
export const useProduct =
(plugin?: ProductPlugin, config: Config = {}): UseProduct =>
(sku?: string) => {
const [response, setResponse] = useState(createResponse<Product>());
useEffect(() => {
if (!sku) return;
const fetchProduct = getProduct(plugin, config);
fetchProduct(sku).then(productResponse => {
setResponse(productResponse);
});
}, [config, sku]);
return response;
};
Augmenting with a Commerce Function
Utilizing a commerce function that consolidates the initialization of hooks and non-hook functionalities with the necessary plugins, as shown below, ensures that plugins do not need to be provided each time a hook or function is used, enhancing code cleanliness and ease of use:
const commerce = (plugins?: Plugins, config = {}): Commerce => ({
getProduct: getProductBase(plugins?.useProduct, config),
useProduct: useProductBase(plugins?.useProduct, config),
});
Simplifying Usage with a Single Commerce Export
To further streamline the application architecture and reduce redundancy, initialize the commerce function just once in a main or configuration file with the necessary plugins and configurations:
// In a main or configuration file
const configuredCommerce = commerce(plugins, config);
export default configuredCommerce;
By exporting this initialized commerce, any component or file that imports it will have direct access to the various hooks and functions, pre-configured and ready for use.
Conclusion
In conclusion, the unified plugin-based system architecture offers a powerful, scalable, and maintainable approach for modern web applications that need to interact with multiple backend systems. By abstracting backend complexities and providing a consistent frontend interface, this architecture streamlines development and promotes best practices. The addition of a commerce function further optimizes the architecture, enhancing the ease of use and ensuring a seamless and robust development experience.