Service Stub (Сервисная заглушка)

Паттерн проектирования Service Stub

Паттерн проектирования Service Stub

Описание Service Stub

Ликвидирует зависимость от внешних проблемных сервисов во время тестирования.

Enterprise-системы часто зависят от внешних сервисов, таких как, например, расчёт кредитного рейтинга, ставки налогов и т.п. Любой разработчик, который когда-либо имел дело с такими системами пожалуется на лишнюю зависимость от абсолютно неконтролируемого ресурса. Часто эти сервисы не блещут стабильностью и надёжностью.

В итоге, эти проблемы могут замедлять разработку. Разработчики вынуждены сидеть и ждать, когда удалённый сервис возобновит свою работу или встраивать в код «костыли», чтобы обходить зависимость от сервиса. Ещё хуже, когда не получается выполнять тесты - весь процесс разработки нарушается.

Замена сервиса на заглушку (Service Stub), которая выполняется локально и быстро, улучшит разработку.

Примеры реализации

// Service Stub Pattern in JavaScript
class CreditRatingService {
    async getCreditRating(userId) {
        // Simulate external service call
        console.log(`Calling external credit rating service for user ${userId}`);
        await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate network delay
        return { rating: 750, score: 'Good' };
    }
}

class CreditRatingServiceStub {
    async getCreditRating(userId) {
        // Stub implementation - fast and predictable
        console.log(`Using stub credit rating service for user ${userId}`);
        return { rating: 800, score: 'Excellent' };
    }
}

class PaymentService {
    constructor(creditRatingService) {
        this.creditRatingService = creditRatingService;
    }
    
    async processPayment(userId, amount) {
        const creditRating = await this.creditRatingService.getCreditRating(userId);
        
        if (creditRating.rating >= 700) {
            console.log(`Payment approved for user ${userId}: $${amount}`);
            return { success: true, message: 'Payment approved' };
        } else {
            console.log(`Payment denied for user ${userId}: $${amount}`);
            return { success: false, message: 'Insufficient credit rating' };
        }
    }
}

// Usage in production
const realCreditService = new CreditRatingService();
const productionPaymentService = new PaymentService(realCreditService);

// Usage in testing
const stubCreditService = new CreditRatingServiceStub();
const testPaymentService = new PaymentService(stubCreditService);

// Test the service
async function runTest() {
    const result = await testPaymentService.processPayment(123, 100);
    console.log('Test result:', result);
}

runTest();
<?php
// Service Stub Pattern in PHP
class CreditRatingService {
    public function getCreditRating($userId) {
        // Simulate external service call
        echo "Calling external credit rating service for user $userId\n";
        sleep(1); // Simulate network delay
        return ['rating' => 750, 'score' => 'Good'];
    }
}

class CreditRatingServiceStub {
    public function getCreditRating($userId) {
        // Stub implementation - fast and predictable
        echo "Using stub credit rating service for user $userId\n";
        return ['rating' => 800, 'score' => 'Excellent'];
    }
}

class PaymentService {
    private $creditRatingService;
    
    public function __construct($creditRatingService) {
        $this->creditRatingService = $creditRatingService;
    }
    
    public function processPayment($userId, $amount) {
        $creditRating = $this->creditRatingService->getCreditRating($userId);
        
        if ($creditRating['rating'] >= 700) {
            echo "Payment approved for user $userId: $$amount\n";
            return ['success' => true, 'message' => 'Payment approved'];
        } else {
            echo "Payment denied for user $userId: $$amount\n";
            return ['success' => false, 'message' => 'Insufficient credit rating'];
        }
    }
}

// Usage in production
$realCreditService = new CreditRatingService();
$productionPaymentService = new PaymentService($realCreditService);

// Usage in testing
$stubCreditService = new CreditRatingServiceStub();
$testPaymentService = new PaymentService($stubCreditService);

// Test the service
$result = $testPaymentService->processPayment(123, 100);
echo "Test result: " . json_encode($result) . "\n";
?>
// Service Stub Pattern in Go
package main

import (
    "fmt"
    "time"
)

type CreditRating struct {
    Rating int
    Score  string
}

type CreditRatingService interface {
    GetCreditRating(userId int) CreditRating
}

type RealCreditRatingService struct{}

func (r RealCreditRatingService) GetCreditRating(userId int) CreditRating {
    // Simulate external service call
    fmt.Printf("Calling external credit rating service for user %d\n", userId)
    time.Sleep(1 * time.Second) // Simulate network delay
    return CreditRating{Rating: 750, Score: "Good"}
}

type CreditRatingServiceStub struct{}

func (s CreditRatingServiceStub) GetCreditRating(userId int) CreditRating {
    // Stub implementation - fast and predictable
    fmt.Printf("Using stub credit rating service for user %d\n", userId)
    return CreditRating{Rating: 800, Score: "Excellent"}
}

type PaymentService struct {
    creditRatingService CreditRatingService
}

func NewPaymentService(creditRatingService CreditRatingService) *PaymentService {
    return &PaymentService{creditRatingService: creditRatingService}
}

func (ps *PaymentService) ProcessPayment(userId int, amount float64) map[string]interface{} {
    creditRating := ps.creditRatingService.GetCreditRating(userId)
    
    if creditRating.Rating >= 700 {
        fmt.Printf("Payment approved for user %d: $%.2f\n", userId, amount)
        return map[string]interface{}{
            "success": true,
            "message": "Payment approved",
        }
    } else {
        fmt.Printf("Payment denied for user %d: $%.2f\n", userId, amount)
        return map[string]interface{}{
            "success": false,
            "message": "Insufficient credit rating",
        }
    }
}

// Usage
func main() {
    // Usage in production
    realCreditService := RealCreditRatingService{}
    productionPaymentService := NewPaymentService(realCreditService)
    
    // Usage in testing
    stubCreditService := CreditRatingServiceStub{}
    testPaymentService := NewPaymentService(stubCreditService)
    
    // Test the service
    result := testPaymentService.ProcessPayment(123, 100.0)
    fmt.Printf("Test result: %+v\n", result)
}

Использована иллюстрация с сайта Мартина Фаулера.

Источник