One of common tasks in software development is building a client library for some service API. Over the years I've come up with a way of structuring these libraries, which has served me well. The evolution of my approach went all the way from 2 layer (Business - Client) to now 4 layers. In some specific cases you might need more than these 4, but in usual case it enough. So the layers are:

  1. Business/Domain Layer
  2. Service Layer
  3. Client Layer
  4. Transport Layer

Business/Domain layer

Business layer doesn't really belong to the library itself, it is application specific. This layer contains business logic, which operates in business terms. For example, your use case requires you to create a Google Analystics visitor statistics widget for your backend panel. It is important to note, that use case does not define which protocol do you need to use or which request you need to make. All because it's irrelevant, the use case is still valid regardless of technical details. We can use some pseudocode to describe usage of the client library in the use case: gaService.getVisitorStatistics(). Actually, the real code shouldn't look much different. As we see, business layer makes use of a service class, which belongs to a service layer.

$statistics
$statistics
Service
Service
$service->getStatistics()
$service->getStatistics()
Domain
Domain

Service Layer

Service layer is also application specific and is not shipped with a library. This layer can translate business requirement into Client terms. In the previous layer we have stated, that we need visitor statistics. This layer knows how to instantiate a Client and which request to invoke to get the data. Also here you get the data from the response object and prepare it to pass back to business layer. This layer can be implemented as an Adapter pattern, which adapts Client Library to the required interface.

request(StatisticsRequest)
request(StatisticsRequest)
Service
Service
StatisticsResponse
StatisticsResponse
Client
Client

Client Layer

Now we have crossed application boundry. This is no longer a part of the application, but a distributable and reusable library by itself. Client knows how send requests. It has a public method request(RequestInterface); and that is basically it. In this layer we define Request and Response classes, which contain the that data you pass to the API and receive from the it. If it's a SOAP client, Request can produce XML (or an array, because PHP's SoapClient in WSDL mode is smart enough) or a JSON string. Response must be able to parse response SOAP XML or JSON and provide you all the methods you need to retrieve data. It can be argued, that I'm missing a presenter layer here, which is true. From my experience, if a format changes most of the request structure change as well. So while these concerns are separate, in practice they usually change together.

get($body)
get($body)
Client
Client
$responseBody
$responseBody
Transport
Transport

Transport Layer

This is your HTTP client, SOAP client or any other client, that doesn't care about contents of your request. It's just a transmission tool, a separate library which hides behind an interface (e.g. PSR-18).

GET host HTTP/1.1
GET host HTTP/1.1
Transport
Transport
HTTP/1.1 200 OK
HTTP/1.1 200 OK
API
API

File structure of the client looks like this.

file-structure

Advantages

This structure helps me to create clients blazingly fast.

  • It's easy to test. You can isolate every layer and mock the adjacent layers.
  • Every layer concentrates on a specific concern. If changes are required, they usually involve just one layer.
  • Extending is easy. To create new requests you just need to add new Request classes, no changes to client needed.