Collections Module Architecture

This document outlines the architectural design of the collections module, which handles subscription payment processing across multiple collection workflows.

Overview

The collections module implements a modular architecture that separates concerns between different collection types while maintaining shared core functionality.

Architecture Principles

Separation of Concerns

Each collector type handles specific business logic while delegating common operations to shared components.

Interface-driven Design

Well-defined interfaces provide clear contracts and enable dependency injection for testing.

Composition over Inheritance

Collectors compose functionality rather than inheriting complex hierarchies.

Core Components

Base Collector

The foundational component that provides shared dependencies and common collection operations.

Responsibilities

  • Managing dependencies (users, payments, subscriptions, notifications)

  • Providing controlled access to sensitive operations

  • Implementing core collection logic

  • Handling payment submissions and receipt generation

Interface Contract

type BaseCollectorIface interface {
    attemptCollection(ctx context.Context, state *collectionState) error
    getGBClient() growthbook.API
    getLockClient() dynamo.Locker
    getPaymentsAPI() payments.API
    getUsersAPI() users.API
    getSubRepo() subscriptions.Repository
    getSubHistoryRepo() history.Repository
}

Specialized Collectors

Purpose-built collectors that handle specific collection scenarios by composing base functionality.

Webhook Balance Collector

Processes subscription collections triggered by account balance updates via webhooks.

Key Features: * Real-time balance monitoring * Multiple subscription handling * Feature flag integration

Design Pattern

type WebhookBalanceCollector struct {
    base BaseCollectorIface
}

func (c *WebhookBalanceCollector) CollectWebhookBalance(
    ctx context.Context,
    userID string,
    accountUpdate *txnModels.Account,
) error {
    // Specialized logic for webhook processing
    return c.base.attemptCollection(ctx, state)
}

Data Flow

  1. Input Processing: Specialized collectors receive external events

  2. State Preparation: Collection state is assembled from user data and subscriptions

  3. Validation: Business rules and feature flags are evaluated

  4. Collection Attempt: Base collector executes payment and record updates

  5. Notification: Success receipts are sent to users

Extension Patterns

Adding New Collector Types

  1. Implement specialized collector struct

  2. Compose BaseCollectorIface for shared functionality

  3. Add business logic specific to collection scenario

  4. Register with appropriate worker/handler

Example: Batch Collector

type BatchCollector struct {
    base BaseCollectorIface
}

func NewBatchCollector(base BaseCollectorIface) *BatchCollector {
    return &BatchCollector{base: base}
}

func (b *BatchCollector) ProcessBatch(ctx context.Context, userIDs []string) error {
    for _, userID := range userIDs {
        // Batch-specific logic
        err := b.base.attemptCollection(ctx, state)
        // Handle batch processing concerns
    }
}

Testing Strategy

Unit Testing

  • Mock BaseCollectorIface for testing specialized collectors

  • Test base collector functionality independently

  • Verify interface contracts

Configuration

Collection behavior is controlled through:

  • Feature Flags: Enable/disable collection types per user

  • Runtime Parameters: Collection amounts, retry policies

  • Business Rules: Payment method selection, timing constraints

Monitoring and Observability

Metrics

  • Collection success/failure rates

  • Payment method distribution

Logging

  • Structured logging with correlation IDs

  • Error details for debugging