Back to Blog
Lesson 57 of the Advanced Laravel: Architecture, Scaling & Performance course
LaravelJune 28, 20263 min read

Managing Third-Party API Integrations: The Adapter Pattern in Laravel

Learn to decouple your Laravel application from external APIs using the Adapter pattern. Build resilient services that shield your domain from vendor churn.

laravelphpbackend

Previously in this course, we discussed Database Deadlock Prevention to ensure high-concurrency stability. While database integrity is foundational, your application's external boundaries are equally fragile. Today, we focus on Managing Third-Party API Integrations by moving away from hard-coded vendor logic toward a clean, adapter-based architecture.

When you sprinkle API calls (using Http::get or Guzzle) directly into your controllers or actions, you create a "vendor lock-in" trap. If the third-party API changes its response structure or authentication method, you’re forced to hunt through your codebase to refactor. Instead, we’ll treat external APIs as internal dependencies that must conform to our domain interfaces.

The Adapter Pattern for API Decoupling

The Adapter pattern allows incompatible interfaces to work together. In the context of Laravel, it means your domain logic speaks one language (your defined Interface), while an Adapter handles the translation to the vendor’s proprietary API.

By wrapping third-party APIs in services, we achieve three things:

  1. Consistency: Your application uses the same method names regardless of the underlying vendor.
  2. Testability: You can mock your internal interface without needing to use Http::fake() for every single integration test.
  3. Resilience: You can implement retries, logging, or fallback logic in one place.

Worked Example: Standardizing Payment Gateways

Imagine we need to integrate a payment provider. We don't want our ProcessPayment action to know if we are using Stripe, Braintree, or a custom internal gateway.

1. Define the Domain Interface

First, define a contract that describes what your application needs, not how the API works.

PHP
namespace App\Contracts;

interface PaymentGateway
{
    public function charge(int $amount, string $currency, string $token): bool;
}

2. Create the Adapter

Now, build an implementation for a specific provider. This class encapsulates all vendor-specific headers, URL structures, and error handling.

PHP
namespace App\Services\Payments;

use App\Contracts\PaymentGateway;
use Illuminate\Support\Facades\Http;

class StripeAdapter implements PaymentGateway
{
    public function __construct(private string $apiKey) {}

    public function charge(int $amount, string $currency, string $token): bool
    {
        $response = Http::withToken($this->apiKey)
            ->post('https:#6A9955">//api.stripe.com/v1/charges', [
                'amount' => $amount,
                'currency' => $currency,
                'source' => $token,
            ]);

        return $response->successful();
    }
}

3. Bind in a Service Provider

Register the implementation in your AppServiceProvider or a dedicated PaymentServiceProvider.

PHP
public function register()
{
    $this->app->bind(PaymentGateway::class, function ($app) {
        return new StripeAdapter(config('services.stripe.key'));
    });
}

4. Inject into your Action

Your business logic now depends on the interface, not the implementation.

PHP
class ProcessPayment
{
    public function __construct(private PaymentGateway $gateway) {}

    public function execute(int $amount)
    {
        return $this->gateway->charge($amount, 'usd', 'tok_visa');
    }
}

Hands-on Exercise

Refactor an existing API integration in your project. If you are using a service like Mailgun or AWS SES, create a MailerInterface and a corresponding adapter.

  1. Create a src/Domain/Communication/Contracts/MailerInterface.php.
  2. Move your Mail::send or HTTP-based mailing logic into a class implementing this interface.
  3. Use the Laravel Service Container to swap the implementation.
  4. Verify that your tests still pass using a mock: $this->mock(MailerInterface::class, fn($m) => $m->shouldReceive('send')->once());.

Common Pitfalls

  • Leaky Abstractions: Avoid leaking vendor-specific exceptions into your domain. Catch RequestException inside the adapter and throw a custom PaymentFailedException that your domain logic understands.
  • Over-Engineering: Don't build an abstraction for an API you only call in one place and will never change. Use the Adapter pattern when you have multiple providers or a high likelihood of vendor churn.
  • Ignoring Latency: Even with a clean architecture, the external network is the slowest part of your application. Always wrap these calls in queues or Graceful Degradation patterns to prevent vendor downtime from cascading into your own.

Recap

We've moved beyond simple Integrating Third-Party Services in Laravel by formalizing our external dependencies. By using the Adapter pattern, we ensure that our domain remains pure, our tests remain fast, and our architecture remains flexible enough to swap vendors without a massive refactor.

Up next: We will discuss strategies for API Rate Limiting and Circuit Breakers, ensuring your application stays responsive even when external vendors experience outages.

Similar Posts