Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHORE] Refacto the whole project - module/plugin architecture oriented 🧹 #772

Open
SquirrelDeveloper opened this issue Feb 22, 2025 · 0 comments
Labels
chores Refactoring or small updates

Comments

@SquirrelDeveloper
Copy link
Collaborator

  1. Create a Module Structure

First, let's create a basic folder structure for your modular application:

project-root/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ modules/
β”‚   β”‚   β”œβ”€β”€ users/
β”‚   β”‚   β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”œβ”€β”€ products/
β”‚   β”‚   β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   └── other-modules/
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”œβ”€β”€ config/
β”‚   β”‚   └── types/
β”‚   β”œβ”€β”€ utils/
β”‚   └── app.ts
β”œβ”€β”€ package.json
└── tsconfig.json
  1. Create Module Interface

Define a standard interface for your modules in src/core/types/module.interface.ts:

import { Router } from 'express';

export interface Module {
  router: Router;
  name: string;
  initialize?: () => Promise<void>;
  cleanup?: () => Promise<void>;
}
  1. Create Base Module Implementation

Here's an example of how to implement a module (e.g., src/modules/users/index.ts):

import { Router } from 'express';
import { Module } from '../../core/types/module.interface';

export class UsersModule implements Module {
  public router: Router;
  public name = 'users';

  constructor() {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Import and use your route handlers
    this.router.get('/', this.getAllUsers);
    this.router.post('/', this.createUser);
    // ... other routes
  }

  private getAllUsers = async (req: Request, res: Response): Promise<void> => {
    // Implementation
  };

  private createUser = async (req: Request, res: Response): Promise<void> => {
    // Implementation
  };

  public async initialize(): Promise<void> {
    // Module initialization logic (e.g., database connections)
    console.log('Users module initialized');
  }

  public async cleanup(): Promise<void> {
    // Cleanup logic when shutting down
    console.log('Users module cleanup');
  }
}
  1. Module Registration System

Create a module registration system in src/core/ModuleRegistry.ts:

import { Express } from 'express';
import { Module } from './types/module.interface';

export class ModuleRegistry {
  private modules: Map<string, Module> = new Map();
  private app: Express;

  constructor(app: Express) {
    this.app = app;
  }

  public registerModule(module: Module): void {
    if (this.modules.has(module.name)) {
      throw new Error(`Module ${module.name} is already registered`);
    }

    this.modules.set(module.name, module);
    this.app.use(`/api/${module.name}`, module.router);
  }

  public async initializeAll(): Promise<void> {
    for (const module of this.modules.values()) {
      if (module.initialize) {
        await module.initialize();
      }
    }
  }

  public async cleanupAll(): Promise<void> {
    for (const module of this.modules.values()) {
      if (module.cleanup) {
        await module.cleanup();
      }
    }
  }

  public getModule(name: string): Module | undefined {
    return this.modules.get(name);
  }
}
  1. Application Entry Point

Update your src/app.ts:

import express from 'express';
import { ModuleRegistry } from './core/ModuleRegistry';
import { UsersModule } from './modules/users';
import { ProductsModule } from './modules/products';
// Import other modules

const app = express();
const moduleRegistry = new ModuleRegistry(app);

// Basic middleware
app.use(express.json());

// Register modules
moduleRegistry.registerModule(new UsersModule());
moduleRegistry.registerModule(new ProductsModule());
// Register other modules

// Initialize all modules
moduleRegistry.initializeAll().then(() => {
  app.listen(3000, () => {
    console.log('Server is running on port 3000');
  });
});

// Handle cleanup on shutdown
process.on('SIGTERM', async () => {
  await moduleRegistry.cleanupAll();
  process.exit(0);
});
  1. Environment Configuration

Create a configuration system in src/core/config/index.ts:

import dotenv from 'dotenv';

dotenv.config();

export const config = {
  port: process.env.PORT || 3000,
  database: {
    url: process.env.DATABASE_URL,
    // other database configs
  },
  // other configuration settings
};
  1. Plugin System (Optional)

If you want to add a plugin system, you can create a plugin interface:

// src/core/types/plugin.interface.ts
export interface Plugin {
  name: string;
  version: string;
  install: (app: Express) => Promise<void>;
  uninstall?: () => Promise<void>;
}

And a plugin manager:

// src/core/PluginManager.ts
import { Express } from 'express';
import { Plugin } from './types/plugin.interface';

export class PluginManager {
  private plugins: Map<string, Plugin> = new Map();
  private app: Express;

  constructor(app: Express) {
    this.app = app;
  }

  public async installPlugin(plugin: Plugin): Promise<void> {
    if (this.plugins.has(plugin.name)) {
      throw new Error(`Plugin ${plugin.name} is already installed`);
    }

    await plugin.install(this.app);
    this.plugins.set(plugin.name, plugin);
  }

  public async uninstallPlugin(pluginName: string): Promise<void> {
    const plugin = this.plugins.get(pluginName);
    if (!plugin) {
      throw new Error(`Plugin ${pluginName} is not installed`);
    }

    if (plugin.uninstall) {
      await plugin.uninstall();
    }
    this.plugins.delete(pluginName);
  }
}

This modular architecture provides several benefits:

  1. Separation of Concerns: Each module is self-contained with its own routes, controllers, and services.
  2. Scalability: Easy to add new modules without affecting existing ones.
  3. Maintainability: Code is organized by feature/domain, making it easier to maintain.
  4. Testability: Modules can be tested in isolation.
  5. Flexibility: Easy to enable/disable features by adding/removing modules.

To migrate your existing code to this architecture:

  1. Identify distinct features/domains in your application
  2. Create separate modules for each feature
  3. Move related routes, controllers, and services into their respective modules
  4. Update imports and dependencies
  5. Gradually migrate one module at a time to minimize risk

Remember to:

  • Keep modules loosely coupled
  • Use dependency injection where appropriate
  • Implement proper error handling
  • Add proper logging
  • Use TypeScript interfaces and types for better type safety
  • Document your module APIs

Would you like me to elaborate on any specific aspect of this architecture or help you with implementing a specific part?

AI Assistant

Same question for the frontend, that is utilizeing UMIJS MAX with Ant Design Pro

AI Assistant

I'll help you design a modular architecture for your frontend application using UmiJS MAX and Ant Design Pro. UmiJS already provides some built-in modular capabilities, but let's organize it in a more structured way.
Here's a comprehensive guide to implement a modular frontend architecture:

  1. Folder Structure

First, let's establish a scalable folder structure:

src/
β”œβ”€β”€ modules/
β”‚   β”œβ”€β”€ users/
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”œβ”€β”€ UserList/
β”‚   β”‚   β”‚   β”œβ”€β”€ UserForm/
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”œβ”€β”€ users.ts
β”‚   β”‚   β”‚   └── types.ts
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   └── userService.ts
β”‚   β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”‚   β”œβ”€β”€ list/
β”‚   β”‚   β”‚   β”œβ”€β”€ detail/
β”‚   β”‚   β”‚   └── form/
β”‚   β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”‚   └── useUsers.ts
β”‚   β”‚   └── index.ts
β”‚   β”œβ”€β”€ products/
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   └── index.ts
β”‚   └── shared/
β”‚       β”œβ”€β”€ components/
β”‚       β”œβ”€β”€ hooks/
β”‚       β”œβ”€β”€ utils/
β”‚       └── types/
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ providers/
β”‚   β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ hooks/
β”‚   └── types/
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ routes.ts
β”‚   β”œβ”€β”€ defaultSettings.ts
β”‚   └── config.ts
└── app.tsx
  1. Module Interface

Create a standard module interface in src/core/types/module.ts:

import { ReactNode } from 'react';
import { Route } from '@umijs/max';

export interface Module {
  name: string;
  routes: Route[];
  models?: any[];
  providers?: ReactNode[];
  initialize?: () => Promise<void>;
}
  1. Module Implementation

Here's an example of a module implementation (src/modules/users/index.ts):

import { Module } from '@/core/types/module';
import { userModel } from './models/users';
import { UserProvider } from './providers/UserProvider';

// Import your components and pages
import { UserList } from './pages/list';
import { UserDetail } from './pages/detail';
import { UserForm } from './pages/form';

export const UsersModule: Module = {
  name: 'users',
  
  routes: [
    {
      path: '/users',
      name: 'users',
      routes: [
        {
          path: '/users/list',
          component: 'users/pages/list',
          name: 'users-list',
        },
        {
          path: '/users/:id',
          component: 'users/pages/detail',
          name: 'user-detail',
        },
        {
          path: '/users/new',
          component: 'users/pages/form',
          name: 'user-new',
        },
      ],
    },
  ],
  
  models: [userModel],
  
  providers: [UserProvider],
  
  async initialize() {
    // Module initialization logic
    console.log('Users module initialized');
  },
};

// Export components for external use
export { UserList, UserDetail, UserForm };
  1. Module Registry

Create a module registry to manage all modules (src/core/ModuleRegistry.ts):

import { ReactNode } from 'react';
import { Module } from './types/module';
import { dynamic } from '@umijs/max';

export class ModuleRegistry {
  private static modules: Map<string, Module> = new Map();

  static registerModule(module: Module) {
    if (this.modules.has(module.name)) {
      throw new Error(`Module ${module.name} is already registered`);
    }
    this.modules.set(module.name, module);
  }

  static getAllRoutes() {
    return Array.from(this.modules.values()).flatMap((module) => module.routes);
  }

  static getAllModels() {
    return Array.from(this.modules.values()).flatMap((module) => module.models || []);
  }

  static getAllProviders(): ReactNode[] {
    return Array.from(this.modules.values()).flatMap((module) => module.providers || []);
  }

  static async initializeAll() {
    for (const module of this.modules.values()) {
      if (module.initialize) {
        await module.initialize();
      }
    }
  }
}
  1. Application Setup

Update your src/app.tsx:

import { RunTimeLayoutConfig } from '@umijs/max';
import { ModuleRegistry } from './core/ModuleRegistry';
import { UsersModule } from './modules/users';
import { ProductsModule } from './modules/products';

// Register modules
ModuleRegistry.registerModule(UsersModule);
ModuleRegistry.registerModule(ProductsModule);

// Initialize modules
ModuleRegistry.initializeAll();

// Configure runtime layout
export const layout: RunTimeLayoutConfig = () => {
  return {
    // Your layout configuration
  };
};

// Wrap the app with module providers
export function rootContainer(container: ReactNode) {
  return ModuleRegistry.getAllProviders().reduce(
    (children, Provider) => <Provider>{children}</Provider>,
    container,
  );
}
  1. Route Configuration

Update your config/routes.ts:

import { ModuleRegistry } from '@/core/ModuleRegistry';

export default [
  {
    path: '/',
    layout: true,
    routes: [
      {
        path: '/welcome',
        name: 'welcome',
        component: './Welcome',
      },
      ...ModuleRegistry.getAllRoutes(),
    ],
  },
];
  1. State Management

Create module-specific models (src/modules/users/models/users.ts):

import { Effect, Reducer } from '@umijs/max';
import { message } from 'antd';
import { getUserList } from '../services/userService';

export interface UserState {
  list: User[];
  currentUser: User | null;
}

export default {
  namespace: 'users',
  
  state: {
    list: [],
    currentUser: null,
  },
  
  effects: {
    *fetchUsers({ payload }, { call, put }) {
      try {
        const response = yield call(getUserList, payload);
        yield put({
          type: 'saveUsers',
          payload: response.data,
        });
      } catch (error) {
        message.error('Failed to fetch users');
      }
    },
  },
  
  reducers: {
    saveUsers(state, { payload }) {
      return {
        ...state,
        list: payload,
      };
    },
  },
};
  1. Shared Components and Hooks

Create reusable components and hooks in the shared module:

// src/modules/shared/components/PageContainer/index.tsx
import { PageContainer as AntPageContainer } from '@ant-design/pro-components';
import { ReactNode } from 'react';

interface PageContainerProps {
  title: string;
  children: ReactNode;
}

export const PageContainer = ({ title, children }: PageContainerProps) => {
  return (
    <AntPageContainer title={title}>
      {children}
    </AntPageContainer>
  );
};
  1. Module-Specific Components

Create components for each module:

// src/modules/users/components/UserList/index.tsx
import { Table } from 'antd';
import { useModel } from '@umijs/max';
import styles from './index.less';

export const UserList = () => {
  const { users, loading } = useModel('users');

  const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
    },
    // ... other columns
  ];

  return (
    <Table
      className={styles.userTable}
      columns={columns}
      dataSource={users}
      loading={loading}
    />
  );
};
  1. API Services

Organize API calls in service files:

// src/modules/users/services/userService.ts
import { request } from '@umijs/max';

export const getUserList = async (params: any) => {
  return request('/api/users', {
    method: 'GET',
    params,
  });
};

export const createUser = async (data: any) => {
  return request('/api/users', {
    method: 'POST',
    data,
  });
};
  1. Custom Hooks

Create module-specific hooks:

// src/modules/users/hooks/useUsers.ts
import { useModel } from '@umijs/max';

export const useUsers = () => {
  const { users, loading, fetchUsers } = useModel('users');

  const refreshUsers = async () => {
    await fetchUsers();
  };

  return {
    users,
    loading,
    refreshUsers,
  };
};

This architecture provides several benefits:

  1. Modularity: Each feature is self-contained with its own components, models, and services
  2. Reusability: Shared components and utilities can be easily reused
  3. Maintainability: Code is organized by feature and is easier to maintain
  4. Scalability: New modules can be added without affecting existing ones
  5. Type Safety: TypeScript interfaces ensure type consistency

Best Practices:

  1. Lazy Loading: Use dynamic imports for routes to improve initial load time
  2. State Management: Keep state local to modules when possible
  3. Component Design: Create reusable components in the shared module
  4. Type Safety: Use TypeScript interfaces for all props and state
  5. Testing: Write tests for each module independently

To migrate your existing code:

  1. Create the new folder structure
  2. Move related components and logic into appropriate modules
  3. Update imports and dependencies
  4. Test each module independently
  5. Gradually migrate one feature at a time
@SquirrelDeveloper SquirrelDeveloper moved this from Backlog to For the next release in Squirrel Servers Manager (SSM) Roadmap Feb 22, 2025
@SquirrelDeveloper SquirrelDeveloper added the chores Refactoring or small updates label Feb 22, 2025
@SquirrelDeveloper SquirrelDeveloper changed the title [CHORE] Refacto the whole project - module/plugin architecture oriented [CHORE] Refacto the whole project - module/plugin architecture oriented 🧹 Feb 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
chores Refactoring or small updates
Projects
Status: For the next release
Development

No branches or pull requests

1 participant