Creating Services
Creating Services
Section titled “Creating Services”Learn how to create a new microservice using the Banyan Platform CLI and templates.
Use This Guide If…
Section titled “Use This Guide If…”- You’re creating your first microservice on the platform
- You want to understand the service structure and conventions
- You need to set up a new business domain service
- You’re learning the platform architecture and patterns
Quick Start
Section titled “Quick Start”# Create a new service using the CLInpx @banyanai/platform-cli create my-service-name
# The CLI creates:# - Complete service structure# - Docker Compose configuration# - Sample handlers and contracts# - Testing setup# - All necessary configuration filesService Structure
Section titled “Service Structure”A newly created service has this structure:
my-service/├── docker-compose.yml # Service infrastructure├── package.json # Dependencies and scripts├── tsconfig.json # TypeScript configuration├── jest.config.js # Testing configuration├── packages/│ └── contracts/ # Service contracts (API definitions)│ ├── src/│ │ ├── commands.ts # Command contracts│ │ ├── queries.ts # Query contracts│ │ ├── events.ts # Event contracts│ │ └── index.ts # Export all contracts│ └── package.json└── service/ ├── src/ │ ├── main.ts # Service entry point │ ├── commands/ # Command handlers │ │ └── CreateItemHandler.ts │ ├── queries/ # Query handlers │ │ └── GetItemHandler.ts │ ├── subscriptions/ # Event subscription handlers │ │ └── ItemCreatedHandler.ts │ ├── domain/ # Aggregates and domain logic │ │ └── ItemAggregate.ts │ ├── read-models/ # Read models for queries │ │ └── ItemReadModel.ts │ └── clients/ # Service clients │ └── NotificationServiceClient.ts └── package.jsonService Entry Point
Section titled “Service Entry Point”The main.ts file is the entry point for your service:
import { BaseService } from '@banyanai/platform-base-service';import { Logger } from '@banyanai/platform-telemetry';
async function startService() { try { Logger.info('Starting My Service...');
// One-line service startup await BaseService.start({ serviceName: 'my-service', version: '1.0.0', });
Logger.info('My Service started successfully'); } catch (error) { Logger.error('Failed to start My Service:', error as Error); process.exit(1); }}
// Start the servicestartService();That’s it! The platform handles:
- Handler discovery
- Contract broadcasting
- Message bus connection
- Service registration
- Health monitoring
- Graceful shutdown
Defining Your First Contract
Section titled “Defining Your First Contract”Create contracts in packages/contracts/src/:
import { Command } from '@banyanai/platform-contract-system';
@Command({ description: 'Create a new item', permissions: ['items:create']})export class CreateItemCommand { name!: string; description!: string; price!: number;}
export interface CreateItemResult { itemId: string; name: string; createdAt: string;}import { Query } from '@banyanai/platform-contract-system';
@Query({ description: 'Retrieve item by ID', permissions: ['items:read']})export class GetItemQuery { itemId!: string;}
export interface ItemResult { itemId: string; name: string; description: string; price: number; createdAt: string;}import { DomainEvent } from '@banyanai/platform-contract-system';
@DomainEvent('MyService.Events.ItemCreated', { broadcast: true, description: 'Item was created'})export class ItemCreatedEvent { itemId!: string; name!: string; price!: number; createdAt!: string;}export * from './commands.js';export * from './queries.js';export * from './events.js';Implementing Your First Handler
Section titled “Implementing Your First Handler”Command Handler
Section titled “Command Handler”import { CommandHandler, CommandHandlerDecorator } from '@banyanai/platform-base-service';import type { AuthenticatedUser } from '@banyanai/platform-core';import { Logger } from '@banyanai/platform-telemetry';import { CreateItemCommand, type CreateItemResult } from '../../packages/contracts';
@CommandHandlerDecorator(CreateItemCommand)export class CreateItemHandler extends CommandHandler<CreateItemCommand, CreateItemResult> { constructor() { super(); }
async handle(command: CreateItemCommand, user: AuthenticatedUser | null): Promise<CreateItemResult> { Logger.info('Creating item', { name: command.name });
// Generate ID const itemId = `item-${Date.now()}`;
// Business logic here...
return { itemId, name: command.name, createdAt: new Date().toISOString(), }; }}Query Handler
Section titled “Query Handler”import { QueryHandler, QueryHandlerDecorator } from '@banyanai/platform-base-service';import type { AuthenticatedUser } from '@banyanai/platform-core';import { Logger } from '@banyanai/platform-telemetry';import { GetItemQuery, type ItemResult } from '../../packages/contracts';import { ItemReadModel } from '../read-models/ItemReadModel';
@QueryHandlerDecorator(GetItemQuery)export class GetItemHandler extends QueryHandler<GetItemQuery, ItemResult> { constructor() { super(); }
async handle(query: GetItemQuery, user: AuthenticatedUser | null): Promise<ItemResult> { Logger.info('Getting item', { itemId: query.itemId });
// Query read model const item = await ItemReadModel.findById<ItemReadModel>(query.itemId);
if (!item) { throw new Error('Item not found'); }
return { itemId: item.id, name: item.name, description: item.description, price: item.price, createdAt: item.createdAt, }; }}Event Subscription Handler
Section titled “Event Subscription Handler”import { EventSubscriptionHandler, EventHandlerDecorator } from '@banyanai/platform-base-service';import type { AuthenticatedUser } from '@banyanai/platform-core';import { Logger } from '@banyanai/platform-telemetry';import { ItemCreatedEvent } from '../../packages/contracts';
@EventHandlerDecorator(ItemCreatedEvent)export class ItemCreatedHandler extends EventSubscriptionHandler<ItemCreatedEvent> { constructor() { super(); }
async handle(event: ItemCreatedEvent, user: AuthenticatedUser | null): Promise<void> { Logger.info('Item created event received', { itemId: event.itemId, name: event.name, });
// React to event - update read models, trigger workflows, etc. }}Running Your Service
Section titled “Running Your Service”Development Mode
Section titled “Development Mode”# Start infrastructure (from service directory)docker compose up -d
# Install dependenciespnpm install
# Build the servicepnpm run build
# Start in development modepnpm run devRunning Tests
Section titled “Running Tests”# Run all testspnpm run test
# Run with coveragepnpm run test:coverage
# Run in watch modepnpm run test:watchProduction Mode
Section titled “Production Mode”# Build for productionpnpm run build
# Start the servicepnpm startDocker Compose Configuration
Section titled “Docker Compose Configuration”The generated docker-compose.yml includes all required infrastructure:
version: '3.8'
services: my-service: build: context: . dockerfile: Dockerfile environment: - NODE_ENV=production - MESSAGE_BUS_URL=amqp://rabbitmq:5672 - DATABASE_URL=postgresql://postgres:password@postgres:5432/myservice - SERVICE_DISCOVERY_URL=http://service-discovery:3000 depends_on: - rabbitmq - postgres - service-discovery
rabbitmq: image: rabbitmq:3-management ports: - "5672:5672" - "15672:15672"
postgres: image: postgres:15 environment: - POSTGRES_DB=myservice - POSTGRES_PASSWORD=password volumes: - postgres-data:/var/lib/postgresql/data
service-discovery: image: banyanai/service-discovery:latest ports: - "3000:3000"
volumes: postgres-data:Configuration
Section titled “Configuration”Environment Variables
Section titled “Environment Variables”# Message BusMESSAGE_BUS_URL=amqp://localhost:5672
# DatabaseDATABASE_URL=postgresql://localhost:5432/myservice
# Service DiscoverySERVICE_DISCOVERY_URL=http://localhost:3000
# Service ConfigurationSERVICE_NAME=my-serviceSERVICE_VERSION=1.0.0
# LoggingLOG_LEVEL=info
# Node EnvironmentNODE_ENV=developmentPackage Configuration
Section titled “Package Configuration”service/package.json:
{ "name": "@myorg/my-service", "version": "1.0.0", "type": "module", "scripts": { "build": "tsc", "dev": "tsx watch src/main.ts", "start": "node dist/main.js", "test": "jest", "test:coverage": "jest --coverage", "test:watch": "jest --watch", "lint": "biome check src", "lint:fix": "biome check --apply src", "type-check": "tsc --noEmit" }, "dependencies": { "@banyanai/platform-base-service": "^1.0.0", "@banyanai/platform-core": "^1.0.0", "@banyanai/platform-telemetry": "^1.0.0" }, "devDependencies": { "@biomejs/biome": "^1.4.0", "@types/jest": "^29.5.0", "@types/node": "^20.0.0", "jest": "^29.5.0", "ts-jest": "^29.1.0", "tsx": "^4.0.0", "typescript": "^5.3.0" }}Best Practices
Section titled “Best Practices”Service Naming
Section titled “Service Naming”DO:
user-serviceorder-management-serviceinventory-servicenotification-serviceDON’T:
my-serviceapi-servicebackendservice1Directory Organization
Section titled “Directory Organization”DO:
- Keep handlers in appropriate directories (
commands/,queries/,subscriptions/) - Group related domain logic in
domain/ - Put read models in
read-models/ - Keep service clients in
clients/
DON’T:
- Mix handler types in one directory
- Put business logic in handlers
- Create deep nested structures
Handler Naming
Section titled “Handler Naming”DO:
CreateUserHandler.tsGetUserHandler.tsUserCreatedHandler.tsDON’T:
CreateUser.tsUser.tsUserHandler.tsNext Steps
Section titled “Next Steps”Now that you’ve created your service, continue with:
- Defining Contracts - Create your API contracts
- Writing Handlers - Implement business logic
- Using Service Clients - Call other services
- Data Access Patterns - Work with aggregates and read models
- Testing Services - Write comprehensive tests
Troubleshooting
Section titled “Troubleshooting”Service Won’t Start
Section titled “Service Won’t Start”- Check all environment variables are set
- Verify RabbitMQ is running (
docker ps) - Check PostgreSQL is accessible
- Review logs for specific errors
Handlers Not Discovered
Section titled “Handlers Not Discovered”- Check files are in correct directories
- Verify filenames end with
Handler.ts - Check decorators are present
- Ensure handlers extend correct base class
Contract Not Found
Section titled “Contract Not Found”- Check contract is exported from
packages/contracts/src/index.ts - Verify decorator is present
- Check service started successfully
- Verify service discovery is running
Related Resources
Section titled “Related Resources”- Writing Handlers - Implement handler logic
- Defining Contracts - Create service contracts
- Using Service Clients - Cross-service communication
- Testing Services - Testing strategies
Congratulations! You’ve created your first Banyan Platform microservice. The platform handles all infrastructure concerns, so you can focus on building business value.