# Inter-Service Communication - Detailed Reference

This document contains detailed code examples and implementation patterns for inter-service communication.

## HTTP/REST Service Client

### Service Client Setup

```typescript
// src/core/clients/service-client.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { logger } from '@goodgo/logger';
import { createCircuitBreaker } from '../resilience/circuit-breaker';

export interface ServiceClientConfig {
  baseURL: string;
  serviceName: string;
  timeout?: number;
  retries?: number;
  enableCircuitBreaker?: boolean;
}

export class ServiceClient {
  private client: AxiosInstance;
  private serviceName: string;
  private circuitBreaker?: ReturnType<typeof createCircuitBreaker>;

  constructor(config: ServiceClientConfig) {
    this.serviceName = config.serviceName;

    this.client = axios.create({
      baseURL: config.baseURL,
      timeout: config.timeout || 5000,
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': `${process.env.SERVICE_NAME || 'unknown'}/1.0`,
      },
    });

    this.setupInterceptors();

    if (config.enableCircuitBreaker !== false) {
      this.circuitBreaker = createCircuitBreaker(
        async (requestConfig: AxiosRequestConfig) => {
          return await this.client.request(requestConfig);
        },
        `service-client-${config.serviceName}`,
        {
          timeout: config.timeout || 5000,
          errorThresholdPercentage: 50,
          resetTimeout: 30000,
        }
      );
    }
  }

  private setupInterceptors(): void {
    this.client.interceptors.request.use(
      (config) => {
        if (process.env.INTERNAL_API_KEY) {
          config.headers['X-Service-Auth'] = process.env.INTERNAL_API_KEY;
        }

        const correlationId = config.headers['x-correlation-id'] || this.generateCorrelationId();
        config.headers['x-correlation-id'] = correlationId;

        logger.debug('Service request', {
          service: this.serviceName,
          method: config.method,
          url: config.url,
          correlationId,
        });

        return config;
      },
      (error) => {
        logger.error('Service request error', { service: this.serviceName, error });
        return Promise.reject(error);
      }
    );

    this.client.interceptors.response.use(
      (response) => {
        logger.debug('Service response', {
          service: this.serviceName,
          status: response.status,
          url: response.config.url,
        });
        return response;
      },
      async (error) => {
        if (error.response) {
          logger.warn('Service error response', {
            service: this.serviceName,
            status: error.response.status,
            url: error.config?.url,
            data: error.response.data,
          });
        } else if (error.request) {
          logger.error('Service request timeout', {
            service: this.serviceName,
            url: error.config?.url,
          });
        }

        return Promise.reject(error);
      }
    );
  }

  private generateCorrelationId(): string {
    return `corr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  async request<T = any>(config: AxiosRequestConfig): Promise<T> {
    try {
      const response = this.circuitBreaker
        ? await this.circuitBreaker.fire(config)
        : await this.client.request(config);

      return response.data;
    } catch (error) {
      logger.error('Service request failed', {
        service: this.serviceName,
        url: config.url,
        error: error.message,
      });
      throw error;
    }
  }

  async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'GET', url });
  }

  async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'POST', url, data });
  }

  async put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'PUT', url, data });
  }

  async delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE', url });
  }
}
```

### Usage Example

```typescript
// src/modules/notification/notification-client.service.ts
import { ServiceClient } from '../../core/clients/service-client';

const notificationClient = new ServiceClient({
  baseURL: process.env.NOTIFICATION_SERVICE_URL || 'http://notification-service:5003',
  serviceName: 'notification-service',
  timeout: 5000,
  enableCircuitBreaker: true,
});

export class NotificationClientService {
  async sendNotification(userId: string, message: string): Promise<void> {
    try {
      await notificationClient.post('/api/v1/notifications', {
        userId,
        message,
      });
    } catch (error) {
      logger.error('Failed to send notification', { userId, error });
      throw error;
    }
  }
}
```

## gRPC Communication

### Protocol Buffer Definitions

```protobuf
// proto/user_service.proto
syntax = "proto3";

package goodgo.user.v1;

option go_package = "github.com/goodgo/user-service/proto";

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc StreamUserUpdates(StreamUserUpdatesRequest) returns (stream UserUpdate);
}

message GetUserRequest {
  string user_id = 1;
}

message GetUserResponse {
  User user = 1;
}

message CreateUserRequest {
  string email = 1;
  string name = 2;
}

message CreateUserResponse {
  User user = 1;
}

message User {
  string id = 1;
  string email = 2;
  string name = 3;
  int64 created_at = 4;
}

message StreamUserUpdatesRequest {
  string user_id = 1;
}

message UserUpdate {
  string user_id = 1;
  string action = 2;
  User user = 3;
  int64 timestamp = 4;
}
```

### gRPC Server Implementation

```typescript
// src/modules/user/user.grpc.service.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { UserService } from './user.service';
import { logger } from '@goodgo/logger';

const PROTO_PATH = './proto/user_service.proto';

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const userProto = grpc.loadPackageDefinition(packageDefinition).goodgo?.user?.v1;

export class UserGrpcServer {
  private server: grpc.Server;
  private userService: UserService;

  constructor(userService: UserService) {
    this.userService = userService;
    this.server = new grpc.Server();
    this.setupServices();
  }

  private setupServices(): void {
    if (!userProto?.UserService) {
      throw new Error('UserService proto not loaded');
    }

    this.server.addService(userProto.UserService.service, {
      getUser: this.getUser.bind(this),
      createUser: this.createUser.bind(this),
      streamUserUpdates: this.streamUserUpdates.bind(this),
    });
  }

  private async getUser(
    call: grpc.ServerUnaryCall<any, any>,
    callback: grpc.sendUnaryData<any>
  ): Promise<void> {
    try {
      const { user_id } = call.request;
      const user = await this.userService.findById(user_id);

      if (!user) {
        return callback({
          code: grpc.status.NOT_FOUND,
          message: 'User not found',
        });
      }

      callback(null, {
        user: {
          id: user.id,
          email: user.email,
          name: user.name,
          created_at: user.createdAt.getTime(),
        },
      });
    } catch (error) {
      logger.error('gRPC getUser error', { error });
      callback({
        code: grpc.status.INTERNAL,
        message: error.message,
      });
    }
  }

  private async createUser(
    call: grpc.ServerUnaryCall<any, any>,
    callback: grpc.sendUnaryData<any>
  ): Promise<void> {
    try {
      const { email, name } = call.request;
      const user = await this.userService.create({ email, name });

      callback(null, {
        user: {
          id: user.id,
          email: user.email,
          name: user.name,
          created_at: user.createdAt.getTime(),
        },
      });
    } catch (error) {
      logger.error('gRPC createUser error', { error });
      callback({
        code: grpc.status.INTERNAL,
        message: error.message,
      });
    }
  }

  private streamUserUpdates(call: grpc.ServerWritableStream<any, any>): void {
    const { user_id } = call.request;

    const subscription = this.userService.subscribeToUpdates(user_id, (update) => {
      call.write({
        user_id: update.userId,
        action: update.action,
        user: update.user,
        timestamp: Date.now(),
      });
    });

    call.on('cancelled', () => {
      subscription.unsubscribe();
    });
  }

  start(port: number): void {
    this.server.bindAsync(
      `0.0.0.0:${port}`,
      grpc.ServerCredentials.createInsecure(),
      (error, port) => {
        if (error) {
          logger.error('Failed to start gRPC server', { error });
          return;
        }

        this.server.start();
        logger.info('gRPC server started', { port });
      }
    );
  }

  stop(): Promise<void> {
    return new Promise((resolve) => {
      this.server.tryShutdown(() => {
        logger.info('gRPC server stopped');
        resolve();
      });
    });
  }
}
```

### gRPC Client Implementation

```typescript
// src/core/clients/grpc-client.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { logger } from '@goodgo/logger';

export interface GrpcClientConfig {
  protoPath: string;
  packageName: string;
  serviceName: string;
  serverUrl: string;
  options?: protoLoader.Options;
}

export class GrpcClient {
  private client: any;
  private serviceName: string;

  constructor(config: GrpcClientConfig) {
    this.serviceName = config.serviceName;

    const packageDefinition = protoLoader.loadSync(config.protoPath, {
      keepCase: true,
      longs: String,
      enums: String,
      defaults: true,
      oneofs: true,
      ...config.options,
    });

    const proto = grpc.loadPackageDefinition(packageDefinition);
    const service = this.getNestedProperty(proto, config.packageName);

    if (!service?.[config.serviceName]) {
      throw new Error(`Service ${config.serviceName} not found in proto`);
    }

    this.client = new service[config.serviceName](
      config.serverUrl,
      grpc.credentials.createInsecure()
    );
  }

  private getNestedProperty(obj: any, path: string): any {
    return path.split('.').reduce((current, prop) => current?.[prop], obj);
  }

  async call<TRequest, TResponse>(
    methodName: string,
    request: TRequest,
    metadata?: grpc.Metadata
  ): Promise<TResponse> {
    return new Promise((resolve, reject) => {
      const callMetadata = metadata || new grpc.Metadata();
      if (process.env.INTERNAL_API_KEY) {
        callMetadata.add('x-service-auth', process.env.INTERNAL_API_KEY);
      }

      this.client[methodName](request, callMetadata, (error: any, response: TResponse) => {
        if (error) {
          logger.error('gRPC call error', {
            service: this.serviceName,
            method: methodName,
            error: error.message,
          });
          reject(error);
          return;
        }

        resolve(response);
      });
    });
  }

  createStream<TRequest, TResponse>(
    methodName: string,
    request: TRequest,
    metadata?: grpc.Metadata
  ): grpc.ClientReadableStream<TResponse> {
    const callMetadata = metadata || new grpc.Metadata();
    if (process.env.INTERNAL_API_KEY) {
      callMetadata.add('x-service-auth', process.env.INTERNAL_API_KEY);
    }

    return this.client[methodName](request, callMetadata);
  }
}
```

### gRPC Client Usage

```typescript
// src/modules/user/user.grpc.client.ts
import { GrpcClient } from '../../core/clients/grpc-client';

const userGrpcClient = new GrpcClient({
  protoPath: './proto/user_service.proto',
  packageName: 'goodgo.user.v1',
  serviceName: 'UserService',
  serverUrl: process.env.USER_SERVICE_GRPC_URL || 'localhost:50051',
});

export class UserGrpcClientService {
  async getUser(userId: string) {
    return userGrpcClient.call('getUser', { user_id: userId });
  }

  async createUser(email: string, name: string) {
    return userGrpcClient.call('createUser', { email, name });
  }

  streamUserUpdates(userId: string, callback: (update: any) => void) {
    const stream = userGrpcClient.createStream('streamUserUpdates', {
      user_id: userId,
    });

    stream.on('data', callback);
    stream.on('error', (error) => {
      logger.error('gRPC stream error', { error });
    });
  }
}
```

## GraphQL Communication

### GraphQL Schema Definition

```graphql
# schema/user.graphql
type Query {
  user(id: ID!): User
  users(filter: UserFilter, pagination: Pagination): UserConnection
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

type User {
  id: ID!
  email: String!
  name: String
  createdAt: DateTime!
  updatedAt: DateTime!
}

input CreateUserInput {
  email: String!
  name: String
}

input UpdateUserInput {
  name: String
}

input UserFilter {
  email: String
  name: String
}

input Pagination {
  skip: Int
  take: Int
}

type UserConnection {
  nodes: [User!]!
  total: Int!
  hasNextPage: Boolean!
}

scalar DateTime
```

### GraphQL Server (Apollo)

```typescript
// src/modules/user/user.graphql.service.ts
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { UserService } from './user.service';
import { logger } from '@goodgo/logger';
import { readFileSync } from 'fs';
import { resolvers } from './user.resolvers';

const typeDefs = readFileSync('./schema/user.graphql', 'utf-8');

export function createGraphQLServer(userService: UserService): ApolloServer {
  return new ApolloServer({
    typeDefs,
    resolvers: resolvers(userService),
    plugins: [
      {
        requestDidStart() {
          return {
            didResolveOperation(requestContext) {
              logger.debug('GraphQL operation', {
                operation: requestContext.operationName,
                query: requestContext.request.query,
              });
            },
            didEncounterErrors(requestContext) {
              logger.error('GraphQL errors', {
                errors: requestContext.errors,
              });
            },
          };
        },
      },
    ],
  });
}

// src/modules/user/user.resolvers.ts
export function resolvers(userService: UserService) {
  return {
    Query: {
      user: async (_: any, { id }: { id: string }) => {
        return await userService.findById(id);
      },
      users: async (
        _: any,
        { filter, pagination }: { filter?: any; pagination?: any }
      ) => {
        const result = await userService.findAll({ filter, pagination });
        return {
          nodes: result.users,
          total: result.total,
          hasNextPage: result.users.length === (pagination?.take || 10),
        };
      },
    },
    Mutation: {
      createUser: async (_: any, { input }: { input: any }) => {
        return await userService.create(input);
      },
      updateUser: async (_: any, { id, input }: { id: string; input: any }) => {
        return await userService.update(id, input);
      },
      deleteUser: async (_: any, { id }: { id: string }) => {
        await userService.delete(id);
        return true;
      },
    },
  };
}
```

### GraphQL Client

```typescript
// src/core/clients/graphql-client.ts
import { GraphQLClient } from 'graphql-request';
import { logger } from '@goodgo/logger';

export interface GraphQLClientConfig {
  endpoint: string;
  headers?: Record<string, string>;
  timeout?: number;
}

export class GraphQLServiceClient {
  private client: GraphQLClient;
  private endpoint: string;

  constructor(config: GraphQLClientConfig) {
    this.endpoint = config.endpoint;
    this.client = new GraphQLClient(config.endpoint, {
      headers: {
        'Content-Type': 'application/json',
        ...(process.env.INTERNAL_API_KEY && {
          'x-service-auth': process.env.INTERNAL_API_KEY,
        }),
        ...config.headers,
      },
      timeout: config.timeout || 5000,
    });
  }

  async query<T = any>(query: string, variables?: any): Promise<T> {
    try {
      const data = await this.client.request<T>(query, variables);
      return data;
    } catch (error) {
      logger.error('GraphQL query error', {
        endpoint: this.endpoint,
        error: error.message,
      });
      throw error;
    }
  }

  async mutate<T = any>(mutation: string, variables?: any): Promise<T> {
    try {
      const data = await this.client.request<T>(mutation, variables);
      return data;
    } catch (error) {
      logger.error('GraphQL mutation error', {
        endpoint: this.endpoint,
        error: error.message,
      });
      throw error;
    }
  }
}

// Usage example
const userGraphQLClient = new GraphQLServiceClient({
  endpoint: process.env.USER_SERVICE_GRAPHQL_URL || 'http://user-service:5002/graphql',
});

const GET_USER_QUERY = `
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      email
      name
      createdAt
    }
  }
`;

export async function getUser(userId: string) {
  return userGraphQLClient.query(GET_USER_QUERY, { id: userId });
}
```

## Service-to-Service Authentication

### Internal Authentication Middleware

```typescript
// src/middlewares/internal-auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { logger } from '@goodgo/logger';

export const internalAuthMiddleware = (
  req: Request,
  res: Response,
  next: NextFunction
): void => {
  const serviceAuthToken = req.headers['x-service-auth'];

  if (!serviceAuthToken) {
    logger.warn('Missing service auth token', {
      ip: req.ip,
      path: req.path,
    });
    res.status(401).json({
      success: false,
      error: {
        code: 'INTERNAL_AUTH_REQUIRED',
        message: 'Service authentication required',
      },
    });
    return;
  }

  if (serviceAuthToken !== process.env.INTERNAL_API_KEY) {
    logger.warn('Invalid service auth token', {
      ip: req.ip,
      path: req.path,
    });
    res.status(403).json({
      success: false,
      error: {
        code: 'INVALID_SERVICE_AUTH',
        message: 'Invalid service authentication',
      },
    });
    return;
  }

  req.serviceContext = {
    authenticated: true,
    timestamp: new Date(),
  };

  next();
};
```

### Mutual TLS (mTLS) for gRPC

```typescript
// src/config/grpc-tls.config.ts
import * as grpc from '@grpc/grpc-js';
import { readFileSync } from 'fs';

export function createServerCredentials(): grpc.ServerCredentials {
  return grpc.ServerCredentials.createSsl(
    null,
    [
      {
        cert_chain: readFileSync(process.env.GRPC_SERVER_CERT_PATH!),
        private_key: readFileSync(process.env.GRPC_SERVER_KEY_PATH!),
      },
    ],
    true
  );
}

export function createClientCredentials(): grpc.ChannelCredentials {
  return grpc.credentials.createSsl(
    readFileSync(process.env.GRPC_CA_CERT_PATH!),
    readFileSync(process.env.GRPC_CLIENT_KEY_PATH!),
    readFileSync(process.env.GRPC_CLIENT_CERT_PATH!)
  );
}
```

## Connection Pooling

### HTTP Connection Pool

```typescript
// src/core/clients/connection-pool.ts
import { ServiceClient } from './service-client';

export class ServiceClientPool {
  private pools: Map<string, ServiceClient> = new Map();

  getClient(serviceName: string, config: ServiceClientConfig): ServiceClient {
    if (!this.pools.has(serviceName)) {
      this.pools.set(serviceName, new ServiceClient(config));
    }
    return this.pools.get(serviceName)!;
  }

  clearPool(serviceName: string): void {
    this.pools.delete(serviceName);
  }

  clearAll(): void {
    this.pools.clear();
  }
}

export const serviceClientPool = new ServiceClientPool();
```

## Error Handling

### Service Error Classes

```typescript
// src/core/errors/service-errors.ts
export class ServiceUnavailableError extends Error {
  constructor(public serviceName: string, message?: string) {
    super(message || `Service ${serviceName} is unavailable`);
    this.name = 'ServiceUnavailableError';
  }
}

export class ServiceTimeoutError extends Error {
  constructor(public serviceName: string, public timeout: number) {
    super(`Service ${serviceName} request timed out after ${timeout}ms`);
    this.name = 'ServiceTimeoutError';
  }
}

export class ServiceError extends Error {
  constructor(
    public serviceName: string,
    public statusCode: number,
    message: string,
    public details?: any
  ) {
    super(message);
    this.name = 'ServiceError';
  }
}
```

## Testing Patterns

### Mocking Service Clients

```typescript
// src/__tests__/mocks/service-client.mock.ts
export const createMockServiceClient = () => {
  return {
    get: jest.fn(),
    post: jest.fn(),
    put: jest.fn(),
    delete: jest.fn(),
    request: jest.fn(),
  };
};

// Usage in tests
const mockUserClient = createMockServiceClient();
mockUserClient.get.mockResolvedValue({ id: '123', email: 'test@example.com' });
```

### Integration Testing

```typescript
// src/__tests__/integration/service-communication.e2e.ts
describe('Service Communication E2E', () => {
  it('should communicate with user service via HTTP', async () => {
    const client = new ServiceClient({
      baseURL: process.env.USER_SERVICE_URL || 'http://localhost:5002',
      serviceName: 'user-service',
    });

    const user = await client.get('/api/v1/users/123');
    expect(user).toHaveProperty('id');
  });
});
```
