UNPKG

@iprokit/service

Version:

Powering distributed systems with simplicity and speed.

459 lines (309 loc) 13.7 kB
[![Service Logo](https://www.iprotechs.com/iprokit/service_1.jpg)](https://github.com/iprokit/service) Powering distributed systems with simplicity and speed. [![npm version](https://img.shields.io/npm/v/@iprokit/service.svg)](https://www.npmjs.com/package/@iprokit/service) ![npm](https://img.shields.io/npm/dm/@iprokit/service) ![Node.js](https://img.shields.io/node/v/@iprokit/service) ![TypeScript](https://img.shields.io/badge/types-TypeScript-blue) [![License: Apache-2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](./LICENSE) ![Maintenance](https://img.shields.io/maintenance/yes/2025) ![Made with love](https://img.shields.io/badge/made%20with-%E2%9D%A4-red) # Service `Service` is a powerful, lightweight framework designed to simplify the development of efficient, reliable, and scalable applications. Whether you're developing a monolithic system or a suite of interconnected microservices, `Service` simplifies the process with minimal configuration. # Features - **Hypertext Transfer Protocol (HTTP):** Define and manage routes for handling web requests with support for dynamic and wildcard paths. - **Service Communication Protocol (SCP):** Create robust inter-service communication with remote functions, broadcasts, and workflows. - **Service Discovery Protocol (SDP):** Dynamically discover and link services for seamless interaction in distributed systems. # Installation ```sh npm install @iprokit/service --save ``` # Quick Start Here’s how to get a basic service running: ```javascript import Service from '@iprokit/service'; // Create a service instance. const service = new Service('MyService'); // Listen for service events. service.on('start', () => console.log('Service has started successfully.')); service.on('stop', () => console.log('Service has stopped successfully.')); // Start the service. await service.start(3000, 6000, 5000, '224.0.0.2'); console.log('Service is running: HTTP Port: 3000, SCP Port: 6000, SDP Port: 5000, Multicast Address: 224.0.0.2.'); ``` # HTTP (Hypertext Transfer Protocol) The **HTTP** module provides the tools to define, expose, and manage routes for handling client requests efficiently. ## Routes Create HTTP routes to handle client requests with ease. The framework supports all standard HTTP methods and offers flexible options for advanced routing. ### Get Handle `GET` requests to retrieve resources: ```javascript service.get('/products', (request, response) => { response.end('Retrieve all products'); }); ``` ### Post Handle `POST` requests to create resources: ```javascript service.post('/products', (request, response) => { response.end('Create a new product'); }); ``` ### Put Handle `PUT` requests to update resources entirely: ```javascript service.put('/products/:id', (request, response) => { response.end(`Update product with ID: ${request.params.id}`); }); ``` ### Patch Handle `PATCH` requests to update resources partially: ```javascript service.patch('/products/:id', (request, response) => { response.end(`Partially update product with ID: ${request.params.id}`); }); ``` ### Delete Handle `DELETE` requests to delete resources: ```javascript service.delete('/products/:id', (request, response) => { response.end(`Delete product with ID: ${request.params.id}`); }); ``` ### All Handle any HTTP method for a specific route: ```javascript service.all('/products', (request, response) => { response.end('Handle all methods for /products'); }); ``` ## Dynamic Parameters Capture and use dynamic URL parameters to create flexible routes: ```javascript service.get('/products/:id', (request, response) => { const { id } = request.params; response.end(`Retrieve product with ID: ${id}`); }); ``` ## Query Parameters Extract and use query parameters from the request URL: ```javascript service.get('/products/search', (request, response) => { const { category, price } = request.query; response.end(`Search products in category: ${category} with price: ${price}`); }); ``` ## Multiple Handlers Chain multiple handlers for modular request processing. Use `next()` to pass control: ```javascript const validateRequest = (request, response, next) => { console.log('Validating request'); next(); }; const processRequest = (request, response) => { response.end('Request processed successfully'); }; service.get('/products/process', validateRequest, processRequest); ``` ## Router Organize and manage related routes with the `Router` class. ### Create and Mount Routers Create a router and mount it to the service: ```javascript import { Router } from '@iprokit/service'; const router = new Router(); router.get('/users', (request, response) => { response.end('Get all users'); }); service.mount('/', router); ``` ### Mount Routes on the Same Path Mount multiple routers on the same path to handle different functionalities: ```javascript const userRouter = new Router(); const productRouter = new Router(); userRouter.get('/users', (request, response) => { response.end('User route'); }); productRouter.get('/products', (request, response) => { response.end('Product route'); }); service.mount('/', userRouter, productRouter); ``` ### Mount Routes on a Parent Router Nest routes under a parent router for hierarchical organization: ```javascript const apiRouter = new Router(); const userRouter = new Router(); userRouter.get('/profile', (request, response) => { response.end('User profile'); }); apiRouter.mount('/users', userRouter); service.mount('/api', apiRouter); ``` ## Wildcard Paths Use wildcard routes to match dynamic patterns. ### Match Routes Catch-all handler for unmatched routes: ```javascript service.get('*', (request, response) => { response.end(`No matching route for: ${request.url}`); }); ``` ### Match Nested Routes Match nested paths under a specific route: ```javascript service.get('/categories/*', (request, response) => { response.end(`Nested path: ${request.url}`); }); ``` ### Match Prefix-Based Routes Match routes starting with a specific prefix: ```javascript service.get('/prod*', (request, response) => { response.end(`Matched prefix route: ${request.url}`); }); ``` # SDP (Service Discovery Protocol) The **SDP** module allows services to automatically discover and connect to each other within a network. This simplifies the process of linking services and ensures seamless communication between them. ## Linking Services ### Discoverable Target Service A service can be configured to become discoverable on the network, allowing other services to link to it. ```javascript import Service from '@iprokit/service'; // Create a discoverable service instance. const serviceA = new Service('ServiceA'); // Start the service to make it discoverable. await serviceA.start(3000, 6000, 5000, '224.0.0.2'); console.log('ServiceA is discoverable on SDP (Port: 5000, Address: 224.0.0.2).'); ``` ### Linking to a Target Service A service can link to a discoverable target service using the `RemoteService` class. This establishes a connection during startup, enabling communication between the two services. ```javascript import Service, { RemoteService } from '@iprokit/service'; // Create a service instance. const serviceB = new Service('ServiceB'); // Link to ServiceA using the RemoteService class. const remoteToA = new RemoteService('RemoteToA'); serviceB.link('ServiceA', remoteToA); // Start the service. await serviceB.start(4000, 7000, 5000, '224.0.0.2'); console.log('ServiceB is running and linked to ServiceA.'); ``` # SCP (Service Communication Protocol) The **SCP** module enables seamless interaction between services by defining remote functions, broadcasting messages, and coordinating complex workflows. SCP supports various communication modes, making it easy to tailor inter-service interactions to your system’s requirements. ## Broadcast Broadcast messages to notify multiple services simultaneously. This is useful for system-wide updates, alerts, or notifications. Send a broadcast to all subscribed services: ```javascript await serviceA.broadcast('Catalog.updated', { id: 'P12345', name: 'Wireless Headphones' }); ``` Subscribe to broadcast events and handle incoming messages: ```javascript remoteToA.on('Catalog.updated', (product) => { console.log(`Product Details: ID - ${product.id}, Name - ${product.name}`); }); ``` ## Execution (Remote Functions) SCP allows services to expose and call remote functions for direct, targeted communication. ### Message/Reply The message-reply model enables direct communication between two services. This pattern is particularly suited for synchronous operations where the caller needs an immediate reply from the callee. Expose a reply function to fetch product details: ```javascript serviceA.reply('getProduct', (productId) => { return { id: productId, name: 'Wireless Headphones', price: 99.99, stock: 25 }; }); ``` Call a remote function to retrieve product details: ```javascript const product = await remoteToA.message('getProduct', 'P12345'); console.log(`Product Details:`, product); ``` ### Conduct/Conductor The conduct-conductor model facilitates multi-step workflows across services. It enables coordinated operations with support for signaling (e.g., COMMIT, ROLLBACK) to ensure consistency in distributed workflows. Expose a conductor function to handle multi-step workflows: ```javascript serviceA.conductor('createOrder', (conductor, orderDetails) => { console.log(`Processing order:`, orderDetails); conductor.on('signal', (event, tags) => { console.log(`${event} signal received.`); conductor.signal(event, tags); }); conductor.on('end', () => conductor.end()); }); ``` Coordinate conductors across multiple services: ```javascript import { Coordinator } from '@iprokit/service'; const coordinator = new Coordinator(); try { console.log('Starting workflow.'); // Conduct operations across services. await remoteToA.conduct('createOrder', coordinator, { orderId: 'O123' }); console.log('Order validated.'); await remoteToB.conduct('processPayment', coordinator, { paymentId: 'P456' }); console.log('Payment processed.'); console.log('Sending COMMIT signal.'); await coordinator.signal('COMMIT'); } catch (error) { console.error('Error occurred. Sending ROLLBACK signal.', error); await coordinator.signal('ROLLBACK'); } finally { console.log('Ending coordinator.'); await coordinator.end(); } ``` ### Omni The Omni mode acts as a catch-all handler for operations that don’t match a specific function. It is particularly useful for handling undefined or broad operation patterns in a flexible and generic way. ```javascript serviceA.omni('order', (incoming, outgoing) => { outgoing.end(`Operation '${incoming.operation}' completed.`); }); ``` ## Executor The Executor class organizes and manages remote functions within a service. Executors are ideal for creating modular, reusable logic for handling various operations. ### Create and Attach Executors Create an executor and attach it to a service to handle specific operations: ```javascript const executor = new Executor(); executor.reply('get', (userId) => { return { id: userId, name: 'John Doe', email: 'johndoe@example.com' }; }); serviceA.attach('User', executor); ``` ### Attach Executions on the Same Operation The Omni mode allows multiple handlers to process the same operation in steps. Use `proceed()` to pass control from one handler to the next. Processing an Order in Steps: ```javascript executor.omni('processOrder', (incoming, outgoing, proceed) => { console.log('Step 1: Validating order'); proceed(); // Pass control to the next handler }); executor.omni('processOrder', (incoming, outgoing, proceed) => { console.log('Step 2: Checking inventory'); proceed(); // Pass control to the next handler }); executor.omni('processOrder', (incoming, outgoing) => { console.log('Step 3: Completing the order'); outgoing.end('Order processed successfully.'); }); ``` **Note:** The **Omni** mode is the only SCP mode that supports `proceed()` and allows multiple handlers for the same operation. Each handler in Omni mode shares the same `incoming` and `outgoing` objects, enabling step-by-step processing within the same execution context. Other modes (e.g., Reply, Conductor) do not support `proceed()` or multi-handler execution. ## Wildcard Operations Wildcard operations provide dynamic handling for undefined or broad operation patterns. This is useful for logging, debugging, or fallback logic. ### Match All Unmatched Operations Catch all unmatched operations using a wildcard handler: ```javascript executor.omni('*', (incoming, outgoing) => { outgoing.end(`No handler defined for operation: ${incoming.operation}`); }); ``` ### Match Operations with Specific Prefixes Match operations dynamically based on a prefix to group related tasks: ```javascript executor.omni('inventory*', (incoming, outgoing) => { outgoing.end(`Handled operation: ${incoming.operation}`); }); ``` # Contributing We welcome contributions to [`@iprokit/service`](https://github.com/iprokit/service)! Whether it's reporting bugs, suggesting enhancements, or submitting pull requests — your help is appreciated. To contribute: 1. Fork the repository. 2. Create a new branch for your feature or fix. 3. Submit a pull request with a clear description of your changes. Please ensure your code is clean, well-documented, and covered by tests where appropriate. For major changes or questions, feel free to open an issue to discuss your ideas first. # License This project is licensed under the Apache License 2.0 – see the [LICENSE](./LICENSE) and [NOTICE](./NOTICE) files for details.