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)
}
Использована иллюстрация с сайта Мартина Фаулера.