Symfony – How to Consume External RESTful API with Symfony

apimicroservicesPHPrestsymfony

We are building a Microservice architecture for our projects, with mostly front-end Symfony applications interacting with back-end RESTful APIs.

The problem is this approach is breaking the Symfony entity management relying heavily on Doctrine with database. Where Symfony usually handle entities with Doctrine, automating most of the work, this cannot be reproduced easily when we have to access external data from the APIs.

For example, with a Client entity:

  • Using Doctrine, we just have to define our Client class, and it is now easy to create, update, retrieve our clients
  • Using the REST API approach, clients are accessible through the API, but we have a lot of work to define how the client is created (POST), updated (PUT), retrieved (GET), etc.

To be noted clients are used by several applications, not only the front-end app, hence the dedicated API.

Should we create classes with entity-like methods hiding the API calls complexity, importing all the API data locally and access them through Doctrine, or any other way?

Best Answer

I've made a symfony-based project that is using an external API (JSON); what I did was creating an independent client library ("client library" - a piece of software, composer package), with it's own set of entities (POPOs); it integrates with the framework using interfaces provided by Symfony (for example, by simply creating a custom user provider).

The client makes http calls "behind-the-scenes" - that is important for future testing capabilities. You don't want to expose the way you communicate with your data source and also you don't want your tests to rely on live api.

Client library interface (example how it may look like):

class ApiClient {

   /**
    * @throws SomeApiException If credentials are invalid
    * @return ApiUser
    */
   public function authenticate($username, $password);

   /**
    * @return ApiUser
    */
   public function findUserByEmail($email);

   /**
    * @throws SomeApiException If email is invalid
    * @return void
    */
   public function changeUserEmail(User $user, $newEmail);
}

The client library internally uses Guzzle for communication and Doctrine Cache component for caching the results. The mapping between entity objects and json was made by mappers, that once written - did not change very often (or event at all). In this case I would suggest using the JMS Serializer for an automated transformation to and from JSON (I assume that you use JSON).

You will need a good caching mechanism and local storage, like Redis. Making api-calls on each app request will kill your server and drastically slow down your application. It's very important to understand how http caches work. If your api does not use caching headers (or uses it in an obscure way) it will be very difficult and resource-consuming to keep track of the changes.

You will also want to think about how the client should behave if the connection breaks - should the client use stalled data? It would be a good idea to use some proxy server between your app and the API. In this case the proxy (like Varnish) could speed up your requests and also refresh stalled data in the background without slowing down your app. It will also keep your website online in case of an API failure. You might not be able to write data in the meantime, but your users will still be able to browse cached data.

And speaking of Doctrine, see the "Law of the instrument".

Related Topic