Skip to main content

Overview

The SubscriberService is responsible for managing subscriber information in OmniView. A subscriber is a unique identifier that represents an Oracle Advanced Queuing (AQ) listener registered to receive tracer events from the OMNI_TRACER_QUEUE. Key responsibilities:
  • Generating unique subscriber identifiers using UUID v4
  • Persisting subscriber information to BoltDB
  • Registering subscribers with Oracle AQ
  • Retrieving existing subscriber configurations
The service follows the hexagonal architecture pattern and acts as the bridge between persistent storage (BoltDB) and the Oracle database queue system.

Service Structure

type SubscriberService struct {
    db   ports.DatabaseRepository
    bolt ports.ConfigRepository
}
db
ports.DatabaseRepository
Database repository for Oracle operations, used to register subscribers with Oracle AQ
bolt
ports.ConfigRepository
Configuration repository for BoltDB operations, used to persist subscriber information locally

Subscriber Entity

type Subscriber struct {
    Name      string
    BatchSize int
    WaitTime  int
}
Name
string
Unique subscriber identifier (e.g., “SUB_A1B2C3D4_E5F6_7890_ABCD_EF1234567890”)
BatchSize
int
Number of messages to dequeue in a single batch operation (default: 1000)
WaitTime
int
Wait time in seconds for dequeue operations before timing out (default: 5)

Constructor

NewSubscriberService

Creates a new instance of SubscriberService with injected dependencies.
func NewSubscriberService(
    db ports.DatabaseRepository,
    bolt ports.ConfigRepository,
) *SubscriberService
db
ports.DatabaseRepository
required
Database repository interface for Oracle database operations
bolt
ports.ConfigRepository
required
Configuration repository interface for BoltDB operations
*SubscriberService
*SubscriberService
Returns a pointer to the newly created SubscriberService instance
Example Usage:
// From cmd/omniview/main.go:62
subscriberService := subscribers.NewSubscriberService(dbAdapter, boltAdapter)

Methods

RegisterSubscriber

Retrieves an existing subscriber from BoltDB or creates a new one if not found. After ensuring a subscriber exists locally, it registers the subscriber as a listener in the Oracle database. This is the primary method used during application startup to ensure a valid subscriber is available for event listening.
func (ss *SubscriberService) RegisterSubscriber() (domain.Subscriber, error)
subscriber
domain.Subscriber
The registered subscriber with Name, BatchSize, and WaitTime populated
error
error
Returns error if retrieval, creation, or registration fails, nil on success
Process Flow:
  1. Attempt to retrieve existing subscriber from BoltDB
  2. If subscriber not found (ErrSubscriberNotFound), create a new subscriber
  3. Register the subscriber with Oracle AQ
  4. Return the subscriber configuration
Example Usage:
// From cmd/omniview/main.go:77-81
subscriber, err := subscriberService.RegisterSubscriber()
if err != nil {
    log.Fatalf("failed to register subscriber: %v", err)
}
fmt.Printf("Registered Subscriber: %s\n", subscriber.Name)

NewSubscriber

Generates a new unique subscriber with a UUID-based name and stores it in BoltDB.
func (ss *SubscriberService) NewSubscriber() (domain.Subscriber, error)
subscriber
domain.Subscriber
Newly created subscriber with:
  • Name: UUID v4-based unique identifier (format: “SUB_[UUID]”)
  • BatchSize: 1000 (default)
  • WaitTime: 5 seconds (default)
error
error
Returns error if storage to BoltDB fails, nil on success
Subscriber Name Format: The subscriber name is generated using the following pattern:
  • Prefix: SUB_
  • UUID v4 with hyphens replaced by underscores
  • Uppercase
  • Example: SUB_A1B2C3D4_E5F6_7890_ABCD_EF1234567890
// From internal/service/subscribers/subscriber_service.go:76-84
func generateSubscriberName() string {
    uuidWithHyphen := uuid.New()
    subscriberName := "SUB_" + strings.ToUpper(
        strings.ReplaceAll(uuidWithHyphen.String(), "-", "_")
    )
    return subscriberName
}

SetSubscriber

Stores the subscriber configuration in BoltDB.
func (ss *SubscriberService) SetSubscriber(subscriber domain.Subscriber) error
subscriber
domain.Subscriber
required
Subscriber configuration to store persistently
error
error
Returns error if storage operation fails, nil on success

GetSubscriber

Retrieves the subscriber configuration from BoltDB.
func (ss *SubscriberService) GetSubscriber() (*domain.Subscriber, error)
subscriber
*domain.Subscriber
Pointer to the stored subscriber configuration, or nil if not found
error
error
Returns domain.ErrSubscriberNotFound if no subscriber exists, or other errors on storage failures

Integration with Oracle AQ

The SubscriberService integrates with Oracle Advanced Queuing through the Oracle adapter’s RegisterNewSubscriber method. This creates a subscription in Oracle that allows the application to receive messages from the OMNI_TRACER_QUEUE.

Oracle Subscription Details

When a subscriber is registered with Oracle:
  1. A subscription is created with the subscriber name
  2. The subscription is linked to the OMNI_TRACER_QUEUE
  3. The subscriber can then dequeue messages specific to that subscription
  4. Multiple subscribers can listen to the same queue independently
Oracle Adapter Method:
// From internal/core/ports/repository.go:17
RegisterNewSubscriber(subscriber domain.Subscriber) error
This method is implemented in the Oracle adapter (internal/adapter/storage/oracle) and handles the Oracle-specific subscription creation using Oracle AQ procedures.

Integration with BoltDB

Subscriber information is persisted to BoltDB to ensure:
  • The same subscriber ID is reused across application restarts
  • No duplicate subscriptions are created in Oracle
  • Fast retrieval without database queries
BoltDB Methods Used:
// From internal/core/ports/repository.go:38-39
SetSubscriber(subscriber domain.Subscriber) error
GetSubscriber() (*domain.Subscriber, error)

Usage Flow

Error Handling

ErrSubscriberNotFound

var ErrSubscriberNotFound = errors.New("subscriber name not found")
This error is returned when attempting to retrieve a subscriber that doesn’t exist in BoltDB. The RegisterSubscriber method specifically handles this error to trigger new subscriber creation. Example Error Handling:
subscriber, err := ss.GetSubscriber()
if err != nil {
    if !errors.Is(err, domain.ErrSubscriberNotFound) {
        return domain.Subscriber{}, err // Other errors
    }
    // Subscriber not found - create new one
    newSubscriber, err := ss.NewSubscriber()
    if err != nil {
        return domain.Subscriber{}, err
    }
    subscriber = &newSubscriber
}

Why UUID-based Subscriber Names?

The service uses UUID v4 for subscriber name generation to ensure:
  1. Uniqueness: No naming conflicts even with multiple OmniView instances
  2. No Central Coordination: Each instance can generate its own unique ID
  3. Oracle Compatibility: Uses underscores instead of hyphens to comply with Oracle naming conventions
  4. Traceability: The SUB_ prefix makes it easy to identify OmniView subscribers in Oracle

Architecture Pattern

SubscriberService follows the hexagonal architecture (ports and adapters) pattern:
  1. Core Service (internal/service/subscribers): Contains business logic
  2. Ports (internal/core/ports): Define repository interfaces
  3. Adapters:
    • internal/adapter/storage/boltdb: Implements ConfigRepository
    • internal/adapter/storage/oracle: Implements DatabaseRepository
This design provides:
  • Clear separation of concerns
  • Dependency inversion
  • Easy testing with mock repositories
  • Flexibility to change storage implementations

Build docs developers (and LLMs) love