Skip to main content

Service Hierarchy Extensibility Guide

Purpose: Guide for adding new service types and extending existing hierarchies.


Adding a New Service Type

Step 1: Define Service Type Characteristics

Before implementation, answer these questions:

QuestionAnswer FormatExample
What is the base/root service?Service name"Satellite Internet Base"
What resources are allocated?List of INVENTORY servicesDish, Modem, IP Address
What optional features exist?List of ADDON servicesSpeed Boost, Static IP, Data Packs
Is regulatory tracking needed?Yes/No + which registriesNo (satellite not in IPND)
Is there nested hierarchy?Yes/No + diagramNo (flat like Mobile)

Step 2: Create Hierarchy Specification Document

Create a new file: .planning/phases/XX/research/{service-type}-hierarchy-specification.md

Template:

# {Service Type} Hierarchy Specification

## Overview
- **Service Type**: {Name}
- **Base Service Code**: SVC_{TYPE}_BASE
- **Hierarchy Pattern**: {Flat/Nested/Shared}
- **Max Depth**: {1/2/3}

## Service Catalogue

### PRIMARY Service
| Code | Role | Parent | Inclusion |
|------|------|--------|-----------|
| SVC_{TYPE}_BASE | PRIMARY | null | N/A |

### INVENTORY Services
| Code | Role | Parent | Inclusion | Pool |
|------|------|--------|-----------|------|
| SVC_{TYPE}_DEVICE | INVENTORY | SVC_{TYPE}_BASE | OPTIONAL | device_pool |

### ADDON Services
| Code | Role | Parent | Inclusion | Price |
|------|------|--------|-----------|-------|
| SVC_{TYPE}_DATA | ADDON | SVC_{TYPE}_BASE | OPTIONAL | $X |

### REGISTRY Services (if applicable)
| Code | Role | Parent | Inclusion | Registry |
|------|------|--------|-----------|----------|
| N/A | - | - | - | - |

## Hierarchy Diagram

\`\`\`mermaid
flowchart TD
BASE[SVC_{TYPE}_BASE<br/>PRIMARY] --> DEVICE[SVC_{TYPE}_DEVICE<br/>INVENTORY]
BASE --> DATA[SVC_{TYPE}_DATA<br/>ADDON]
\`\`\`

## Activation Flow
1. Customer purchases SVC_{TYPE}_BASE
2. User selects OPTIONAL children
3. MANDATORY children auto-activate
4. Inventory allocated for INVENTORY services
5. Carrier notification (if applicable)

Step 3: Create Service Templates

Database Migration:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">

<changeSet id="staqr-{type}-service-templates" author="developer">
<!-- PRIMARY: Base Service -->
<insert tableName="cat_service_template">
<column name="code" value="SVC_{TYPE}_BASE"/>
<column name="description" value="{Type} Base Service"/>
<column name="role" value="PRIMARY"/>
<column name="parent_service_template_id" valueComputed="null"/>
<column name="inclusion_type" valueComputed="null"/>
<column name="sort_order" value="1"/>
<column name="auto_activate_children" valueBoolean="true"/>
<column name="inherit_parent_lifecycle" valueBoolean="false"/>
</insert>

<!-- INVENTORY: Device -->
<insert tableName="cat_service_template">
<column name="code" value="SVC_{TYPE}_DEVICE"/>
<column name="description" value="{Type} Device"/>
<column name="role" value="INVENTORY"/>
<column name="parent_service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_{TYPE}_BASE')"/>
<column name="inclusion_type" value="OPTIONAL"/>
<column name="sort_order" value="10"/>
<column name="inherit_parent_lifecycle" valueBoolean="true"/>
</insert>

<!-- ADDON: Data -->
<insert tableName="cat_service_template">
<column name="code" value="SVC_{TYPE}_DATA"/>
<column name="description" value="{Type} Data Pack"/>
<column name="role" value="ADDON"/>
<column name="parent_service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_{TYPE}_BASE')"/>
<column name="inclusion_type" value="OPTIONAL"/>
<column name="sort_order" value="20"/>
<column name="inherit_parent_lifecycle" valueBoolean="true"/>
</insert>
</changeSet>
</databaseChangeLog>

Step 4: Add TypeScript Types

File: packages/shared/src/lib/catalog/service-composition.types.ts

// Add to ServiceTypeCode enum
export enum ServiceTypeCode {
// ... existing codes
{TYPE} = '{TYPE}',
}

// Add to service type mapping
export const SERVICE_TYPE_MAPPING: Record<ServiceTypeCode, ServiceRole[]> = {
// ... existing mappings
[{TYPE}]: [
ServiceRole.PRIMARY, // SVC_{TYPE}_BASE
ServiceRole.INVENTORY, // SVC_{TYPE}_DEVICE
ServiceRole.ADDON, // SVC_{TYPE}_DATA, etc.
],
};

Step 5: Configure Carrier Integration (if applicable)

File: packages/server/api/src/app/carrier/adapters/{type}-carrier.adapter.ts

@Injectable()
export class {Type}CarrierAdapter extends CarrierAdapterBase {
readonly carrierType = CarrierType.{TYPE};

async activate(service: ServiceInstance): Promise<CarrierResult> {
// Implement carrier-specific activation
}

async suspend(service: ServiceInstance): Promise<CarrierResult> {
// Implement carrier-specific suspension
}

async terminate(service: ServiceInstance): Promise<CarrierResult> {
// Implement carrier-specific termination
}
}

Step 6: Add to UI Service Configurator

File: packages/react-ui/src/features/catalog/components/ServiceConfigurator/

  1. Add service type icon
  2. Add service type label translations
  3. Configure hierarchy display for new type

Step 7: Test New Service Type

# 1. Run migration
pnpm run migration:run

# 2. Verify templates created
psql -c "SELECT code, role, parent_service_template_id FROM cat_service_template WHERE code LIKE 'SVC_{TYPE}%';"

# 3. Test activation via API
curl -X POST "https://api.staqr.com/v1/services/activate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceCode": "SVC_{TYPE}_BASE", "customerId": "TEST123"}'

# 4. Verify hierarchy in UI
# Navigate to service configurator and select new type

Extending Existing Hierarchies

Adding a New Child Service

Step 1: Determine Service Characteristics

AttributeValueRationale
RoleINVENTORY, ADDON, or REGISTRYBased on service role decision tree
ParentSVC_PARENT_*Based on parent selection tree
Inclusion TypeMANDATORY, OPTIONAL, or CONFIGURABLEBased on inclusion type tree
Sort OrderNPosition among siblings

Step 2: Create Database Migration

<changeSet id="staqr-add-{new-service}" author="developer">
<insert tableName="cat_service_template">
<column name="code" value="SVC_{NEW_SERVICE}"/>
<column name="description" value="{New Service Description}"/>
<column name="role" value="{ROLE}"/>
<column name="parent_service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_{PARENT}')"/>
<column name="inclusion_type" value="{INCLUSION_TYPE}"/>
<column name="sort_order" value="{SORT_ORDER}"/>
<column name="inherit_parent_lifecycle" valueBoolean="true"/>
</insert>
</changeSet>

Step 3: Add Charges (if applicable)

<changeSet id="staqr-add-{new-service}-charges" author="developer">
<insert tableName="cat_charge_template">
<column name="service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_{NEW_SERVICE}')"/>
<column name="charge_type" value="RECURRING"/>
<column name="amount" value="{PRICE}"/>
<column name="currency_code" value="AUD"/>
</insert>
</changeSet>

Step 4: Add Eligibility Constraints (if CONFIGURABLE)

<changeSet id="staqr-add-{new-service}-constraints" author="developer">
<insert tableName="cat_eligibility_constraint">
<column name="service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_{NEW_SERVICE}')"/>
<column name="constraint_type" value="CUSTOMER_TIER"/>
<column name="constraint_value" value="PREMIUM"/>
</insert>
</changeSet>

Converting Standalone Services to Hierarchical

Step 1: Identify Standalone Services

-- Find services that should be children but have no parent
SELECT code, role, description
FROM cat_service_template
WHERE role IN ('INVENTORY', 'ADDON', 'REGISTRY')
AND parent_service_template_id IS NULL;

Step 2: Map to Parent Services

Standalone ServiceCorrect ParentRationale
SVC_EXAMPLE_ADDONSVC_MOBILE_BASEMobile-specific feature
SVC_SHARED_FEATURE(multiple)Shared across types

Step 3: Create Migration to Set Parents

<changeSet id="staqr-fix-standalone-services" author="developer">
<!-- Set parent for Mobile-specific -->
<update tableName="cat_service_template">
<column name="parent_service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_MOBILE_BASE')"/>
<where>code = 'SVC_EXAMPLE_ADDON'</where>
</update>

<!-- For shared services, may need multiple parent support -->
</changeSet>

Step 4: Migrate Existing Instances

-- Update existing service instances to have correct parent
UPDATE billing_service_instance si
SET parent_service_id = (
SELECT parent_si.id
FROM billing_service_instance parent_si
JOIN cat_service_template parent_st ON parent_si.service_template_id = parent_st.id
WHERE parent_st.code = 'SVC_MOBILE_BASE'
AND parent_si.subscription_id = si.subscription_id
)
WHERE si.service_template_id = (
SELECT id FROM cat_service_template WHERE code = 'SVC_EXAMPLE_ADDON'
)
AND si.parent_service_id IS NULL;

Step 5: Validate Migration

-- Verify no orphaned services remain
SELECT code FROM cat_service_template
WHERE role IN ('INVENTORY', 'ADDON', 'REGISTRY')
AND parent_service_template_id IS NULL;

-- Should return 0 rows

Adding Nested Hierarchy (Multi-Level)

When to Use Nested Hierarchy

Use nested hierarchy when:

  • Technical dependency exists (NBN_AVC depends on NBN_ACCESS)
  • Logical grouping required (VOICEMAIL → VOICEMAIL_BASIC, VOICEMAIL_VISUAL)
  • Activation order matters (parent must activate before child)

Example: Adding NBN Speed Tiers Under NBN_ACCESS

flowchart TD
BASE[SVC_NBN_BASE<br/>PRIMARY] --> ACCESS[SVC_NBN_ACCESS<br/>ADDON]
ACCESS --> AVC[SVC_NBN_AVC<br/>ADDON]
ACCESS --> SPEED_25[SVC_NBN_SPEED_25<br/>ADDON]
ACCESS --> SPEED_50[SVC_NBN_SPEED_50<br/>ADDON]
ACCESS --> SPEED_100[SVC_NBN_SPEED_100<br/>ADDON]

Migration

<changeSet id="staqr-add-nbn-speed-tiers" author="developer">
<!-- Speed tier services as children of NBN_ACCESS (not NBN_BASE) -->
<insert tableName="cat_service_template">
<column name="code" value="SVC_NBN_SPEED_25"/>
<column name="role" value="ADDON"/>
<column name="parent_service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_NBN_ACCESS')"/>
<column name="inclusion_type" value="OPTIONAL"/>
<column name="sort_order" value="100"/>
</insert>

<insert tableName="cat_service_template">
<column name="code" value="SVC_NBN_SPEED_50"/>
<column name="role" value="ADDON"/>
<column name="parent_service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_NBN_ACCESS')"/>
<column name="inclusion_type" value="OPTIONAL"/>
<column name="sort_order" value="110"/>
</insert>

<insert tableName="cat_service_template">
<column name="code" value="SVC_NBN_SPEED_100"/>
<column name="role" value="ADDON"/>
<column name="parent_service_template_id"
valueComputed="(SELECT id FROM cat_service_template WHERE code = 'SVC_NBN_ACCESS')"/>
<column name="inclusion_type" value="OPTIONAL"/>
<column name="sort_order" value="120"/>
</insert>
</changeSet>

Testing New Hierarchies

Comprehensive Checklist

Template Configuration Tests

  • All services have correct role
  • All non-PRIMARY services have parent
  • PRIMARY services have no parent
  • Sort order is set for all children
  • Inclusion type is appropriate
  • Lifecycle inheritance is configured

Activation Tests

  • PRIMARY service activates successfully
  • MANDATORY children auto-activate
  • OPTIONAL children can be selected/deselected
  • CONFIGURABLE children respect eligibility
  • INVENTORY services allocate resources
  • Carrier notifications sent (if applicable)

Lifecycle Tests

  • Parent suspension cascades to children
  • Parent termination cascades to children
  • Child can be terminated independently (if allowed)
  • Reactivation works correctly

UI Tests

  • Hierarchy displays correctly in service configurator
  • Correct services are selectable
  • Eligibility indicators work
  • Pricing displays correctly
  • Order summary is accurate

Automated Test Script

#!/bin/bash
# test-hierarchy.sh

SERVICE_TYPE=$1

echo "=== Testing $SERVICE_TYPE Hierarchy ==="

# 1. Check templates exist
echo "Checking templates..."
psql -c "
SELECT code, role, inclusion_type
FROM cat_service_template
WHERE code LIKE 'SVC_${SERVICE_TYPE}%'
ORDER BY sort_order;
"

# 2. Verify parent relationships
echo "Checking parent relationships..."
psql -c "
SELECT st.code, parent.code as parent_code
FROM cat_service_template st
LEFT JOIN cat_service_template parent ON st.parent_service_template_id = parent.id
WHERE st.code LIKE 'SVC_${SERVICE_TYPE}%';
"

# 3. Check for orphans
echo "Checking for orphans..."
ORPHANS=$(psql -t -c "
SELECT COUNT(*) FROM cat_service_template
WHERE code LIKE 'SVC_${SERVICE_TYPE}%'
AND role != 'PRIMARY'
AND parent_service_template_id IS NULL;
")

if [ "$ORPHANS" -gt 0 ]; then
echo "❌ Found $ORPHANS orphaned services!"
exit 1
fi

echo "✅ All checks passed"

Performance Considerations

Hierarchy Depth Limits

  • Recommended max depth: 3 levels
  • Hard limit: 5 levels (performance degrades beyond this)
  • Reason: Recursive queries become expensive at deep levels

Indexing Requirements

-- Essential indexes for hierarchy queries
CREATE INDEX idx_service_template_parent ON cat_service_template(parent_service_template_id);
CREATE INDEX idx_service_template_role ON cat_service_template(role);
CREATE INDEX idx_service_instance_parent ON billing_service_instance(parent_service_id);
CREATE INDEX idx_service_instance_subscription ON billing_service_instance(subscription_id);

Caching Strategy

  • Cache service templates (rarely change)
  • Don't cache service instances (frequently change)
  • Invalidate cache on template updates

Backwards Compatibility

Adding New Services

  • Safe: New services can be added without affecting existing
  • Migration: Run in non-production first

Changing Parent Relationships

  • Risky: May break existing service instances
  • Migration: Update both templates AND instances
  • Rollback: Keep old parent until instances migrated

Removing Services

  • Very Risky: May orphan existing instances
  • Process:
    1. Mark as deprecated (don't delete)
    2. Migrate existing instances
    3. Delete only when no instances remain

Rollback Procedures

Rolling Back Template Changes

-- Identify changed templates
SELECT code, updated_date
FROM cat_service_template
WHERE updated_date > NOW() - INTERVAL '1 hour';

-- Restore from backup (example)
INSERT INTO cat_service_template
SELECT * FROM cat_service_template_backup
WHERE code = 'SVC_AFFECTED_SERVICE';

Rolling Back Instance Changes

-- Find affected instances
SELECT id, status, parent_service_id
FROM billing_service_instance
WHERE updated_date > NOW() - INTERVAL '1 hour';

-- Restore parent relationships
UPDATE billing_service_instance
SET parent_service_id = (SELECT parent_service_id FROM billing_service_instance_backup WHERE id = ?)
WHERE id = ?;