platform/doc/pipeline_package_specification.md
2024-11-25 20:33:45 -07:00

11 KiB

Pipeline Package Specification

Overview

The Pipeline package provides a robust implementation of the pipeline pattern, allowing for the sequential processing of tasks through a series of stages. It integrates deeply with our Route, Bus, and Queue packages while maintaining Laravel compatibility.

Related Documentation

Core Features

1. Pipeline Base

/// Core pipeline class with conditional execution
class Pipeline<TPassable> with Conditionable<Pipeline<TPassable>> {
  final Container _container;
  final List<Pipe<TPassable>> _pipes;
  TPassable? _passable;
  String _method = 'handle';
  
  Pipeline(this._container, [List<Pipe<TPassable>>? pipes])
      : _pipes = pipes ?? [];
  
  /// Sends an object through the pipeline
  Pipeline<TPassable> send(TPassable passable) {
    _passable = passable;
    return this;
  }
  
  /// Sets the stages of the pipeline
  Pipeline<TPassable> through(List<dynamic> pipes) {
    for (var pipe in pipes) {
      if (pipe is String) {
        // Resolve from container
        _pipes.add(_container.make(pipe));
      } else if (pipe is Type) {
        _pipes.add(_container.make(pipe));
      } else if (pipe is Pipe<TPassable>) {
        _pipes.add(pipe);
      } else if (pipe is Function) {
        _pipes.add(FunctionPipe(pipe));
      }
    }
    return this;
  }
  
  /// Sets the method to call on the pipes
  Pipeline<TPassable> via(String method) {
    _method = method;
    return this;
  }
  
  /// Process the pipeline to final result
  Future<TResult> then<TResult>(
    FutureOr<TResult> Function(TPassable) destination
  ) async {
    var pass = _passable;
    if (pass == null) {
      throw PipelineException('No passable object provided');
    }
    
    // Build pipeline
    var pipeline = _pipes.fold<Function>(
      destination,
      (next, pipe) => (passable) => 
        _container.call(() => pipe.handle(passable, next))
    );
    
    // Execute pipeline
    return await pipeline(pass);
  }
}

2. Middleware Pipeline

/// HTTP middleware pipeline with route integration
class MiddlewarePipeline extends Pipeline<Request> {
  final Router _router;
  
  MiddlewarePipeline(Container container, this._router)
      : super(container);
  
  /// Adds route-specific middleware
  MiddlewarePipeline throughRoute(Route route) {
    // Get global middleware
    var middleware = _router.middleware;
    
    // Add route middleware
    if (route.middleware.isNotEmpty) {
      middleware.addAll(
        route.middleware.map((m) => _container.make<Middleware>(m))
      );
    }
    
    // Add route group middleware
    if (route.group != null) {
      middleware.addAll(route.group!.middleware);
    }
    
    return through(middleware);
  }
  
  /// Processes request through middleware
  Future<Response> process(
    Request request,
    FutureOr<Response> Function(Request) destination
  ) {
    return send(request)
      .when(() => shouldProcessMiddleware(request))
      .then(destination);
  }
  
  /// Checks if middleware should be processed
  bool shouldProcessMiddleware(Request request) {
    return !request.attributes.containsKey('skip_middleware');
  }
}

3. Bus Pipeline

/// Command bus pipeline with handler resolution
class BusPipeline<TCommand> extends Pipeline<TCommand> {
  final CommandBus _bus;
  
  BusPipeline(Container container, this._bus)
      : super(container);
  
  /// Processes command through pipeline
  Future<TResult> process<TResult>(
    TCommand command,
    [Handler<TCommand>? handler]
  ) {
    // Resolve handler
    handler ??= _resolveHandler(command);
    
    return send(command).then((cmd) => 
      handler!.handle(cmd) as Future<TResult>
    );
  }
  
  /// Resolves command handler
  Handler<TCommand> _resolveHandler(TCommand command) {
    if (command is Command) {
      return _container.make(command.handler);
    }
    
    var handlerType = _bus.handlers[TCommand];
    if (handlerType == null) {
      throw HandlerNotFoundException(
        'No handler found for ${TCommand}'
      );
    }
    
    return _container.make(handlerType);
  }
}

4. Job Pipeline

/// Queue job pipeline with middleware
class JobPipeline extends Pipeline<Job> {
  final QueueManager _queue;
  
  JobPipeline(Container container, this._queue)
      : super(container);
  
  /// Processes job through pipeline
  Future<void> process(Job job) {
    return send(job)
      .through(_queue.middleware)
      .then((j) => j.handle());
  }
  
  /// Adds rate limiting
  JobPipeline withRateLimit(int maxAttempts, Duration timeout) {
    return through([
      RateLimitedPipe(maxAttempts, timeout)
    ]);
  }
  
  /// Prevents overlapping jobs
  JobPipeline withoutOverlapping() {
    return through([WithoutOverlappingPipe()]);
  }
}

5. Pipeline Hub

/// Manages application pipelines
class PipelineHub {
  final Container _container;
  final Map<String, Pipeline> _pipelines = {};
  final List<Pipe> _defaults = [];
  
  PipelineHub(this._container);
  
  /// Gets or creates a pipeline
  Pipeline pipeline(String name) {
    return _pipelines.putIfAbsent(
      name,
      () => Pipeline(_container, [..._defaults])
    );
  }
  
  /// Gets middleware pipeline
  MiddlewarePipeline middleware() {
    return pipeline('middleware') as MiddlewarePipeline;
  }
  
  /// Gets bus pipeline
  BusPipeline bus() {
    return pipeline('bus') as BusPipeline;
  }
  
  /// Gets job pipeline
  JobPipeline job() {
    return pipeline('job') as JobPipeline;
  }
  
  /// Sets default pipes
  void defaults(List<Pipe> pipes) {
    _defaults.addAll(pipes);
  }
}

Integration Examples

1. Route Integration

// In RouteServiceProvider
void boot() {
  router.middleware([
    StartSession::class,
    VerifyCsrfToken::class
  ]);
  
  router.group(['middleware' => ['auth']], () {
    router.get('/dashboard', DashboardController);
  });
}

// In Router
Future<Response> dispatch(Request request) {
  var route = matchRoute(request);
  
  return container.make<MiddlewarePipeline>()
    .throughRoute(route)
    .process(request, (req) => route.handle(req));
}

2. Command Bus Integration

// In CommandBus
Future<TResult> dispatch<TResult>(Command command) {
  return container.make<BusPipeline>()
    .through([
      TransactionPipe(),
      ValidationPipe(),
      AuthorizationPipe()
    ])
    .process<TResult>(command);
}

// Usage
class CreateOrder implements Command {
  @override
  Type get handler => CreateOrderHandler;
}

var order = await bus.dispatch<Order>(
  CreateOrder(items: items)
);

3. Queue Integration

// In QueueWorker
Future<void> process(Job job) {
  return container.make<JobPipeline>()
    .withRateLimit(3, Duration(minutes: 1))
    .withoutOverlapping()
    .process(job);
}

// Usage
class ProcessPayment implements Job {
  @override
  Future<void> handle() async {
    // Process payment
  }
}

await queue.push(ProcessPayment(
  orderId: order.id
));

Testing

void main() {
  group('Middleware Pipeline', () {
    test('processes route middleware', () async {
      var pipeline = MiddlewarePipeline(container, router);
      var route = Route('/test', middleware: ['auth']);
      
      var response = await pipeline
        .throughRoute(route)
        .process(request, handler);
        
      verify(() => auth.handle(any, any)).called(1);
    });
  });
  
  group('Bus Pipeline', () {
    test('resolves and executes handler', () async {
      var pipeline = BusPipeline(container, bus);
      var command = CreateOrder(items: items);
      
      var result = await pipeline.process<Order>(command);
      
      expect(result, isA<Order>());
      verify(() => handler.handle(command)).called(1);
    });
  });
}

Next Steps

  1. Add more middleware types
  2. Enhance bus pipeline features
  3. Add job pipeline features
  4. Improve testing coverage
  5. Add performance optimizations

Would you like me to enhance any other package specifications?

Development Guidelines

1. Getting Started

Before implementing pipeline features:

  1. Review Getting Started Guide
  2. Check Laravel Compatibility Roadmap
  3. Follow Testing Guide
  4. Use Foundation Integration Guide
  5. Understand Contracts Package Specification

2. Implementation Process

For each pipeline feature:

  1. Write tests following Testing Guide
  2. Implement following Laravel patterns
  3. Document following Getting Started Guide
  4. Integrate following Foundation Integration Guide

3. Quality Requirements

All implementations must:

  1. Pass all tests (see Testing Guide)
  2. Meet Laravel compatibility requirements
  3. Follow integration patterns (see Foundation Integration Guide)
  4. Implement required contracts (see Contracts Package Specification)

4. Integration Considerations

When implementing pipelines:

  1. Follow patterns in Foundation Integration Guide
  2. Ensure Laravel compatibility per Laravel Compatibility Roadmap
  3. Use testing approaches from Testing Guide
  4. Follow development setup in Getting Started Guide
  5. Implement all contracts from Contracts Package Specification

5. Performance Guidelines

Pipeline system must:

  1. Handle nested pipelines efficiently
  2. Minimize memory usage in long pipelines
  3. Support async operations
  4. Scale with number of stages
  5. Meet performance targets in Laravel Compatibility Roadmap

6. Testing Requirements

Pipeline tests must:

  1. Cover all pipeline types
  2. Test stage ordering
  3. Verify error handling
  4. Check conditional execution
  5. Follow patterns in Testing Guide

7. Documentation Requirements

Pipeline documentation must:

  1. Explain pipeline patterns
  2. Show integration examples
  3. Cover error handling
  4. Include performance tips
  5. Follow standards in Getting Started Guide