Introduction
What is it?
app-compose
is a library for module-based applications.
It helps developers easily connect different parts of an application — features, entities, services, and so on — so they work together as a single system.
With app-compose
, you can:
- Simplify the management of complex dependencies.
- Control the order in which modules run.
- Intuitively enable or disable parts of the application.
- Clearly visualize the parts of the application and their connections.
Instead of manually managing the chaos of modules, app-compose
turns them into a well-organized and scalable application.
Cooking Up Your Application
An application is like a dish: a collection of features, entities, and services. But by themselves, they don’t make an application. To bring everything to life, you need to combine them properly: at the right time, in the right order, and without anything extra. One misstep, and instead of a pizza, you might end up with a cake.
If you’re unsure how to connect modules into a single system, app-compose can simplify the process for you.
Example
import { createContainer, compose } from '@grlt-hub/app-compose';
// Imagine we are cooking a dish in our restaurant kitchen.// There are three steps:// 1. hire the chef// 2. order the ingredients,// 3. and cook the pizza.
// First: prepare the "chef"// it’s like hiring the chef to start cooking.const chef = createContainer({ // The name of our chef. id: 'John Doe', // This chef specializes in Italian cuisine. domain: 'italian-chef', start: async () => { // For example, we are hiring a chef. const hiredChef = await hireChef();
// We return our chef. return { api: hiredChef }; },});
// Second: if the chef is hired,// we need to order the ingredients.const ingredients = createContainer({ id: 'ingredients', domain: 'shop', // The ingredients ordering depends on the chef. dependencies: [chef], // If the chef is on break, // we can't proceed with the order. enable: (api) => api['John Doe'].hasBreak === false, start: async (api) => { // We order the ingredients. const orderedIngredients = await orderIngredients();
// We return the ordered ingredients. return { api: { orderedIngredients } }; },});
// Third: we make the pizza.const pizza = createContainer({ id: 'pizza', domain: 'dish', dependencies: [chef, ingredients], start: (api) => { // The chef uses the ingredients // to make the pizza. const pepperoniPizza = api['John Doe'].makePizza({ ingredients: api.ingredients.orderedIngredients, });
// The pizza is ready! return { api: pepperoniPizza }; },});
// Now the stages: we split the process into steps.// 1: "prepare" — hiring the chef and ordering the ingredients.// 2: "cooking" — making the pizza.const cmd = await compose({ stages: [ ['prepare', [chef, ingredients]], ['cooking', [pizza]], ], // We require everything to be ready. required: 'all',});
// The cooking process has started!await cmd.up();
Example Status Flow
Here’s how the statuses change during the cooking process:
- Initial state:
chef: 'idle', ingredients: 'idle'
— Everything is waiting.chef: 'pending', ingredients: 'idle'
— The chef is on the way to the kitchen.
- If the chef is ready to work:
chef: 'done', ingredients: 'pending'
— Ordering the ingredients.chef: 'done', ingredients: 'done', pizza: 'idle'
— All ingredients have been delivered.chef: 'done', ingredients: 'done', pizza: 'pending'
— Starting to make the pizza.chef: 'done', ingredients: 'done', pizza: 'done'
— The pizza is ready!
- If the chef is here, but taking a break:
chef: 'done', ingredients: 'off', pizza: 'off'
— Cooking is canceled.
Strengths of the Library
- Automatically resolves dependencies, removing the need to manually specify all containers.
- Simplifies working with feature-toggles by eliminating excessive
if/else
logic for disabled functionality. - Allows you to define which parts of the application to run and in what order, prioritizing more important and less important dependencies.
- Offers the ability to visualize the system composed of containers effectively (including transitive dependencies and their paths).
- Provides a simple and intuitive developer experience (DX).
- Ensures high performance, suitable for scalable applications.
- Includes debugging tools to facilitate the development process.
- Covered by 100% tests, including type tests.
What app-compose is NOT
- It does not tell you how to build a module. You choose how your modules work. app-compose only helps you put them together in one app.
- It does not manage data or state. If you need state (like Effector or Redux), you add it inside your modules. app-compose only starts them.