Showing posts with label Promises. Show all posts
Showing posts with label Promises. Show all posts

Wednesday 10 March 2021

TDDing the reading of data from a web service to populate elements of a Vue JS component

G'day:

This article leads on from "Symfony & TDD: adding endpoints to provide data for front-end workshop / registration requirements", which itself continues a saga I started back with "Creating a web site with Vue.js, Nginx, Symfony on PHP8 & MariaDB running in Docker containers - Part 1: Intro & Nginx" (that's a 12-parter), and a coupla other intermediary articles also related to this body of work. What I'm doing here specifically leads on from "Vue.js: using TDD to develop a data-entry form". In that article I built a front-end Vue.js component for a "workshop registration" form, submission, and summary:


Currently the front-end transitions work, but it's not connected to the back-end at all. It needs to read that list of "Workshops to attend" from the DB, and it needs to save all the data between form and summary.

In the immediately previous article I set up the endpoint the front end needs to call to fetch that list of workshops, and today I'm gonna remove the mocked data the form is using, and get it to use reallyreally data from the DB, via that end point.

To recap, this is what we currently have:

In the <template/> section of WorkshopRegistrationForm.vue:

<label for="workshopsToAttend" class="required">Workshops to attend:</label>

<select name="workshopsToAttend[]" multiple="true" required="required" id="workshopsToAttend" v-model="formValues.workshopsToAttend">
    <option v-for="workshop in workshops" :value="workshop.value" :key="workshop.value">{{workshop.text}}</option>
</select>

And in the code part of it:

mounted() {
      this.workshops = this.workshopService.getWorkshops();
},

And WorkshopService just has some mocked data:

getWorkshops() {
    return [
        {value: 2, text:"Workshop 1"},
        {value: 3, text:"Workshop 2"},
        {value: 5, text:"Workshop 3"},
        {value: 7, text:"Workshop 4"}
    ];
}

In this exercise I will be taking very small increments in my test / code / test / code cycle here. It's slightly arbitrary, but it's just because I like to keep separate the "refactoring" parts from the "new development parts", and I'll be doing both. The small increments help surface problems quickly, and also assist my focus so that I'm less likely to stray off-plan and start implementing stuff that is not part of the increment's requirements. This might, however, make for tedious reading. Shrug.

We're gonna refactor things a bit to start with, to push the mocked data closer to the boundary between the application and the DB. We're gonna follow a similar tiered application approach as the back-end work, in that we're going to have these components:

WorkshopRegistrationForm.vue
The Vue component providing the view part of the workshop registration form.
WorkshopService
This is the boundary between WorkshopRegistrationForm and the application code. The component calls it to get / process data. In this exercise, all it needs to do is to return a WorkshopCollection.
WorkshopCollection
This is the model for the collection of Workshops we display. It loads its data via WorkshopRepository
Workshop
This is the model for a single workshop. Just an ID and name.
WorkshopsRepository
This deals with fetching data from storage, and modelling it for the application to use. It knows about the application domain, and it knows about the DB schema. And translates between the two.
WorkshopsDAO
Because the repository has logic in it that we will be testing, our code that actually makes the calls to the DB connector has been extracted into this DAO class, so that when testing the repository it can be mocked-out.
Client
This is not out code. We're using Axios to make calls to the API, and the DAO is a thin layer around this.

As a first step we are going to push that mocked data back to the repository. We can do this without having to change our tests or writing any data manipulation logic.

Here's the current test run:

  Tests of WorkshopRegistrationForm component
     should have a required text input for fullName, maxLength 100, and label 'Full name'
     should have a required text input for phoneNumber, maxLength 50, and label 'Phone number'
     should have a required text input for emailAddress, maxLength 320, and label 'Email address'
     should have a required password input for password, maxLength 255, and label 'Password'
     should have a required workshopsToAttend multiple-select box, with label 'Workshops to attend'
     should list the workshop options fetched from the back-end
     should have a button to submit the registration
     should leave the submit button disabled until the form is filled
     should disable the form and indicate data is processing when the form is submitted
     should send the form values to WorkshopService.saveWorkshopRegistration when the form is submitted
     should display the registration summary 'template' after the registration has been submitted
     should display the summary values in the registration summary

We have to keep that green, and all we can do is move code around. No new code for now (other than a wee bit of scaffolding to let the app know about the new classes). Here are all the first round of changes.

Currently the deepest we go in the new code is the repository, which just returns the mocked data at the moment:

class WorkshopsRepository {

    selectAll() {
        return [
            {value: 2, text:"Workshop 1"},
            {value: 3, text:"Workshop 2"},
            {value: 5, text:"Workshop 3"},
            {value: 7, text:"Workshop 4"}
        ];
    }
}

export default WorkshopsRepository;

The WorkshopCollection grabs that data and uses it to populate itself. It extends the native array class so can be used as an array by the Vue template code:

class WorkshopCollection extends Array {

    constructor(repository) {
        super();
        this.repository = repository;
    }

    loadAll() {
        let workshops = this.repository.selectAll();

        this.length = 0;
        this.push(...workshops);
    }
}

module.exports = WorkshopCollection;

And the WorkshopService has had the mocked values removed, an in their place it tells its WorkshopCollection to load itself, and it returns the populated collection:

class WorkshopService {

    constructor(workshopCollection) {
        this.workshopCollection = workshopCollection;
    }

    getWorkshops() {
        this.workshopCollection.loadAll();
        return this.workshopCollection;
    }

    // ... rest of it unchanged...
}

module.exports = WorkshopService;

WorkshopRegistrationForm.vue has not changed, as it still receives an array of data to its this.workshops property, which is still used in the same way by the template code:

<template>
    <form method="post" action="" class="workshopRegistration" v-if="registrationState !== REGISTRATION_STATE_SUMMARY">
        <fieldset :disabled="isFormDisabled">

            <!-- ... -->

            <select name="workshopsToAttend[]" multiple="true" required="required" id="workshopsToAttend" v-model="formValues.workshopsToAttend">
                <option v-for="workshop in workshops" :value="workshop.value" :key="workshop.value">{{workshop.text}}</option>
            </select>

            <!-- ... -->

        </fieldset>
    </form>

    <!-- ... -->
</template>

<script>
// ...

export default {
    // ...
    mounted() {
        this.workshops = this.workshopService.getWorkshops();
    },
    // ...
}
</script>

Before we wire this up, we need to change our test to reflect where the data is coming from now. It used to be just stubbing the WorkshopService's getWorkshops method, but now we will need to be mocking the WorkshopRepository's selectAll method instead:

workshopService = new WorkshopService();
sinon.stub(workshopService, "getWorkshops").returns(expectedOptions);
sinon.stub(repository, "selectAll").returns(expectedOptions);

The tests now break as we have not yet implemented this change. We need to update App.vue, which has a bit more work to do to initialise that WorkshopService now, with its dependencies:

<template>
    <workshop-registration-form></workshop-registration-form>
</template>

<script>
import WorkshopRegistrationForm from "./WorkshopRegistrationForm";
import WorkshopService from "./WorkshopService";
import WorkshopCollection from "./WorkshopCollection";
import WorkshopsRepository from "./WorkshopsRepository";

let workshopService = new WorkshopService(
    new WorkshopCollection(
        new WorkshopsRepository()
    )
);

export default {
    name: 'App',
    components: {
        WorkshopRegistrationForm
    },
    provide: {
        workshopService: workshopService
    }
}
</script>

Having done this, the tests are passing again.

Next we need to deal with the fact that so far the mocked data we're passing around is keyed on how it is being used in the <option> tags it's populating:

return [
    {value: 2, text:"Workshop 1"},
    {value: 3, text:"Workshop 2"},
    {value: 5, text:"Workshop 3"},
    {value: 7, text:"Workshop 4"}
]

value and text are derived from the values' use in the mark-up, whereas what we ought to be mocking is an array of Workshop objects, which have keys id and name:

class Workshop {

    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
}

module.exports = Workshop;

We will need to update our tests to expect this change:

sinon.stub(workshopService, "getWorkshops").returns(expectedOptions);
sinon.stub(repository, "selectAll").returns(
    expectedOptions.map((option) => new Workshop(option.value, option.text))
);

The tests fail again, so we're ready to change the code to make the tests pass. The repo now returns objects:

class WorkshopsRepository {

    selectAll() {
        return [
            new Workshop(2, "Workshop 1"),
            new Workshop(3, "Workshop 2"),
            new Workshop(5, "Workshop 3"),
            new Workshop(7, "Workshop 4")
        ];
    }
}

And we need to make a quick change to the stubbed saveWorkshopRegistration method in WorkshopService too, so the selectedWorkshops are now filtered on id, not value:

saveWorkshopRegistration(details) {
    let allWorkshops = this.getWorkshops();
    let selectedWorkshops = allWorkshops.filter((workshop) => {
        return details.workshopsToAttend.indexOf(workshop.name) >= 0;
        return details.workshopsToAttend.indexOf(workshop.id) >= 0;
    });
    // ...

And the template now expects them:

<option v-for="workshop in workshops" :value="workshop.value" :key="workshop.value">{{workshop.text}}</option>
<option v-for="workshop in workshops" :value="workshop.id" :key="workshop.id">{{workshop.name}}</option>

I almost forgot about this, but the summary section also uses the WorkshopCollection data, and currently it's also coded to expect mocked workshops with value/text properties, rather than id/name one. So the test needs updating:

it("should display the summary values in the registration summary", async () => {
    const summaryValues = {
        registrationCode : "TEST_registrationCode",
        fullName : "TEST_fullName",
        phoneNumber : "TEST_phoneNumber",
        emailAddress : "TEST_emailAddress",
        workshopsToAttend : [{value: "TEST_workshopToAttend_VALUE", text:"TEST_workshopToAttend_TEXT"}]
        workshopsToAttend : [{id: "TEST_workshopToAttend_ID", name:"TEST_workshopToAttend_NAME"}]
    };
    
	// ...

    let ddValue = actualWorkshopValue.find("ul>li");
    expect(ddValue.exists()).to.be.true;
    expect(ddValue.text()).to.equal(expectedWorkshopValue[0].name);
    expect(ddValue.text()).to.equal(expectedWorkshopValue[0].value);

	// ...
});

And the code change in the template:

<li v-for="workshop in summaryValues.workshopsToAttend" :key="workshop.value">{{workshop.text}}</li>
<li v-for="workshop in summaryValues.workshopsToAttend" :key="workshop.id">{{workshop.name}}</li>

And the tests pass again.

Now we will introduce the WorkshopsDAO with the data that it will get back from the API call mocked. It'll return this to the WorkshopsRepository which will implement the modelling now:

class WorkshopDAO {

    selectAll() {
        return [
            {id: 2, name: "Workshop 1"},
            {id: 3, name: "Workshop 2"},
            {id: 5, name: "Workshop 3"},
            {id: 7, name: "Workshop 4"}
        ];
    }
}

export default WorkshopDAO;

We also now need to update the test initialisation to pass a DAO instance into the repository, and for now we can remove the Sinon stubbing because the DAO is appropriately stubbed already:

let repository = new WorkshopsRepository();
sinon.stub(repository, "selectAll").returns(
    expectedOptions.map((option) => new Workshop(option.value, option.text))
);

workshopService = new WorkshopService(
    new WorkshopCollection(
        repository
        new WorkshopsRepository(
            new WorkshopsDAO()
        )
    )
);

That's the only test change here (and the tests now break). We'll update the code to also pass-in a DAO when we initialise WorkshopService's dependencies, and also implement the modelling code in the WorkshopRepository class's selectAll method:

let workshopService = new WorkshopService(
    new WorkshopCollection(
        new WorkshopsRepository()
        new WorkshopsRepository(
            new WorkshopsDAO()
        )
    )
);
class WorkshopRepository {

    constructor(dao) {
        this.dao = dao;
    }

    selectAll() {
        return this.dao.selectAll().map((unmodelledWorkshop) => {
            return new Workshop(unmodelledWorkshop.id, unmodelledWorkshop.name);
        });
    }
}

We're stable again. The next bit of development is the important bit: add the code in the DAO that actually makes the DB call. We will be changing the DAO to be this:

class WorkshopDAO {

    constructor(client, config) {
        this.client = client;
        this.config = config;
    }

    selectAll() {
        return this.client.get(this.config.workshopsUrl)
            .then((response) => {
                return response.data;
            });
    }
}

export default WorkshopDAO;

And that Config class will be this:

class Config {
    static workshopsUrl = "http://fullstackexercise.backend/workshops/";
}

module.exports = Config;

Now. Because Axios's get method returns a promise, we're going to need to cater to this in the up-stream methods that need manipulate the data being promised. Plus, ultimately, WorkshopRegistration.vue's code is going to receive a promise, not just the raw data. IE, this code:

mounted() {
    this.workshops = this.workshopService.getWorkshops();
    this.workshops = this.workshopService.getWorkshops()
        .then((workshops) => {
            this.workshops = workshops;
        });
},

A bunch of the tests rely on the state of the component after the data has been received:

  • should have a required text input for fullName, maxLength 100, and label 'Full name'
  • should have a required text input for phoneNumber, maxLength 50, and label 'Phone number'
  • should have a required text input for emailAddress, maxLength 320, and label 'Email address'
  • should have a required password input for password, maxLength 255, and label 'Password'
  • should have a required workshopsToAttend multiple-select box, with label 'Workshops to attend'
  • should list the workshop options fetched from the back-end
  • should have a button to submit the registration
  • should leave the submit button disabled until the form is filled
  • should disable the form and indicate data is processing when the form is submitted
  • should send the form values to WorkshopService.saveWorkshopRegistration when the form is submitted
  • should display the registration summary 'template' after the registration has been submitted
  • should display the summary values in the registration summary

In these tests we are gonna have to put the test code within a watch handler, so it waits until the promise returns the data (and accordingly workshops gets set, and the watch-handler fires) before trying to test with it, eg:

it("should list the workshop options fetched from the back-end", () => {
    component.vm.$watch("workshops", async () => {
    	await flushPromises();
        let options = component.findAll("form.workshopRegistration select[name='workshopsToAttend[]']>option");

        expect(options).to.have.length(expectedOptions.length);
        options.forEach((option, i) => {
            expect(option.attributes("value"), `option[${i}] value incorrect`).to.equal(expectedOptions[i].value.toString());
            expect(option.text(), `option[${i}] text incorrect`).to.equal(expectedOptions[i].text);
        });
    });
});

Other than that the tests shouldn't need changing. Also now that the DAO is not itself a mock as it was in the last iteration, we need to go back to mocking it again, this time to return a promise of things to come:

sinon.stub(dao, "selectAll").returns(Promise.resolve(
    expectedOptions.map((option) => ({id: option.value, name: option.text}))
));

It might seem odd that we are making changes to the DAO class but we're not unit testing those changes: the test changes here are only to accommodate the other objects' changes from being synchronous to being asynchrous. Bear in mind that these are unit tests and the DAO only exists as a thin wrapper around the Axios Client object, and its purpose is to give us some of our code that we can mock to prevent Axios from making an actual API call when we test the objects above it. We'll do a in integration test of the DAO separately after we deal with the unit testing and implementation.

After updating those tests to deal with the promises, the tests collapse in a heap, but this is to be expected. We'll make them pass again by bubbling-back the promise from returned from the DAO.

For the code revisions to the other classes I'm just going to show an image of the diff between the files from my IDE. I hate using pictures of code, but this is the clearest way of showing the diffs. All the actual code is on Github @ frontend/src/workshopRegistration

Here in the repository we need to use the values from the promise, so we need to wait for them to be resolved.

Similarly with the collection, service, and component (in two places), as per below:



It's important to note that the template code in the component did not need changing at all: it was happy to wait until the promise resolved before rendering the workshops in the select box.

Having done all this, the tests pass wonderfully, but the UI breaks. Doh! But in an "OK" way:

 


Entirely fair enough. I need to send that header back with the response.

class WorkshopControllerTest extends TestCase
{

    private WorkshopsController $controller;
    private WorkshopCollection $collection;

    protected function setUp(): void
    {
        $this->collection = $this->createMock(WorkshopCollection::class);
        $this->controller = new WorkshopsController($this->collection);
    }

    /**
     * @testdox It makes sure the CORS header returns with the origin's address
     * @covers ::doGet
     */
    public function testDoGetSetsCorsHeader()
    {
        $testOrigin = 'TEST_ORIGIN';
        $request = new Request();
        $request->headers->set('origin', $testOrigin);

        $response = $this->controller->doGet($request);

        $this->assertSame($testOrigin, $response->headers->get('Access-Control-Allow-Origin'));
    }
}
class WorkshopsController extends AbstractController
{

    // ...

    public function doGet(Request $request) : JsonResponse
    {
        $this->workshops->loadAll();

        $origin = $request->headers->get('origin');
        return new JsonResponse(
            $this->workshops,
            Response::HTTP_OK,
            [
                'Access-Control-Allow-Origin' => $origin
            ]
        );
    }
}

That sorts it out. And… we're code complete.

But before I finish, I'm gonna do an integration test of that DAO method, just to automate proof that it does what it needs do. I don't count "looking at the UI and going 'seems legit'" as testing.

import {expect} from "chai";
import Config from "../../src/workshopRegistration/Config";
import WorkshopsDAO from "../../src/workshopRegistration/WorkshopsDAO";
const client = require('axios').default;

describe("Integration tests of WorkshopsDAO", () => {

    it("returns the correct records from the API", async () => {
        let directCallObjects = await client.get(Config.workshopsUrl);

        let daoCallObjects = await new WorkshopsDAO(client, Config).selectAll();

        expect(daoCallObjects).to.eql(directCallObjects.data);
    });
});

And that passes.

OK we're done here. I learned a lot about JS promises in this exercise: I hid a lot of it from you here, but working out how to get those tests working for async stuff coming from the DAO took me an embarassing amount of time and frustration. Once I nailed it it all made sense though, and I was kinda like "duh, Cameron". So that's… erm… good…?

The next thing we need to do is to sort out how to write the data to the DB now. But before we do that, I'm feeling a bit naked without having any data validation on either the form or on the web service. Well: the web service end of things doesn't exist yet, but I'm going to start that work with putting some expected validation in. But I'll put some on the client-side first. As with most of the stuff in this current wodge of work: I do not have the slightest idea of how to do form validation with Vue.js. But tomorrow I will start to find out (see "Vue.js and TDD: adding client-side form field validation" for the results of that). Once again, I have a beer appointment to get myself too for now.

Righto.

--
Adam

Thursday 17 November 2016

PHP: decorating async GuzzleHttp calls

G'day:
A while back I looked at the decorator pattern in the context of some stuff we were doing:

That's all surprisingly straight forward and handy stuff.

We're revisiting some of this for a new implementation of our HTTP adapter library, which had everything baked into it before, and the decorating has had me scratching my head a bit as we're implementing an adapter for GuzzleHttp, and it does its requests asynchronously, and we want to leverage that.

Just to recap, previously we'd have this sort of thing:

namespace me\adamcameron\testApp;

use GuzzleHttp\Client;

class GuzzleAdapter {

    private $client;
    private $endPoint;

    public function __construct($endPoint){
        $this->endPoint = $endPoint;
        $this->client = new Client();
    }

    public function get($id){
        $response = $this->client->requestAsync(
            "get",
            $this->endPoint . $id
        );

        return $response;
    }

}

And say a logging decorator for that:

namespace me\adamcameron\testApp;

class LoggedGuzzleAdapter {

    private $adapter;
    private $logger;

    public function __construct($adapter, $logger) {
        $this->adapter = $adapter;
        $this->logger = $logger;
    }

    public function get($id){
        $this->logger->logMessage(sprintf("Requesting for %s", $id));

        $response = $this->adapter->get($id);

        $body = $response->getBody();
        $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body));
        
        return $response;
    }

}


And we'd init our adapter thus:

$endPoint  = "http://cf2016.local:8516/cfml/misc/guzzleTestEndpoints/getById.cfm?id=";

$guzzleAdapter = new GuzzleAdapter($endPoint);
$logger = new LoggingService();
$adapter = new LoggedGuzzleAdapter($guzzleAdapter, $logger);


So the underlying GuzzleAdapter handles the Guzzle stuff, the LoggedGuzzleAdapter handles just the logging stuff, but defers to its GuzzleAdapter to do its part of the job, and keeps all the moving parts and bits of functionality sensibly separated.  And it's pretty simple. And as detailed in those earlier articles, we can keep layering decorators around an adapter to add caching or what-have-you in a similar way. Easy. Nice.

However this only works cos the call to Guzzle actually returns the result on the spot. And this is cos we were using it synchronously: we make a call to it, it blocks until it gets the answer back from the target and gives us the answer.

Now that we're using async calls, Guzzle doesn't give us the answer, it just gives us a Promise which will eventually resolve to be the answer. This is in theory good cos it means the calling code can make a bunch of HTTP calls, and not wait around for each of them to resolve in series: Guzzle will actually make them all in parallel. I have a look at this in article "PHP: async requests using Guzzle and request pools".

If we go back to our decorator we can see the problem:

public function get($id){
    $this->logger->logMessage(sprintf("Requesting for %s", $id));

    $response$promisedResponse = $this->adapter->get($id);

    $body = $response$promisedResponse->getBody();
    $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body));
    
    return $response$promisedResponse;
}

At the point at which our decorator needs the body... we don't have it yet. All we have is a promise that at some point we'll have a body (or we'll have response object via the resolved promise, anyhow; and the response object will have the body).

To get the body we first need to wait for the promise to resolve... which is a blocking operation and kinda defeats the purpose of using the async approach in the first place. IE: we could do this sort of thing:

public function get($id){
    $this->logger->logMessage(sprintf("Requesting for %s", $id));

    $promisedResponse = $this->adapter->get($id);
    $response = $promisedResponse->wait();
    
    $body = $response->getBody();
    $this->logger->logMessage(sprintf("Response for %s: %s", $id, $body));
    
    return $response;
}

But doing the wait immediately defeats the purpose of making the call async in the first place. We want the wait call to... err... wait... until the calling code says "right I need to actually use that data now".

Our initial attempt to sort this out was to analyse the issue as being one of "well we don't have the data we need until we call wait, so then we need to do the logging then... which means we need to intercept the wait call... which means we need to return our own decorated version of the response from the call to the adapter with its own wait and have a LoggedResponse, and return that from the code above..." But... and I hope my colleague doesn't mind me saying... when I saw this code in code review I kinda went "there must be a better, more semantic way of doing this". I won't repeat the code as I cannot use our actual work code in my blog, and I am not in the office right now and can't remember the detail of the implementation anyhow. But it's not ideal, so I don't want to share it anyhow.

I've been off sick for the last coupla days, which gives me a lot of time to deliberate and mess around with stuff and google a lot more than I allow myself time for when I'm in the office. With a wee bit of "standing back and having another think about it", the solution became clear. We don't need to explicitly override the wait method before we call it... the promise builds that capability in! At the time we're making the call, we get to tell the Promise what happens when it gets resolved. So I've knocked together this proof of concept:

public function get($id){
    $this->logger->logMessage(sprintf("(%s) Requesting for %s", $this->thisFile, $id));

    $response = $this->adapter->get($id);

    $response->then(function($response) use ($id){
        $body = $response->getBody();
        $this->logger->logMessage(sprintf("(%s) Response for %s: %s", $this->thisFile, $id, $body));
        $body->rewind();
    });

    return $response;
}

This was a bit of a head-slap moment - in that it took me a while to work out what to do - the whole thing about Promises is that one's able to chain functionality onto their lifecycle. if we wanna run some code once the Promise resolves, we just sling a then handler onto it.

This is a good solution IMO as it keeps all the logging decoration in one place, doesn't require any blocking of the async part of the operation, and is still very clear what's going on, even if the inner workings of Promises are a bit opaque (which TBH, they still are for me. Every time I work with 'em I have to re-read the docs. Which in Guzzle's case are pretty bloody awful, if I'm to be unkindly frank).

I chucked all this into a test rig to demonstrate to myself it was sound.

First up I contrived an endpoint in CFML for the PHP code to hit. I wanted to do this outside of PHP so it didn't in any way interfere with the PHP code running. It's simple:

<cfscript>
cfcontent(type="application/json");

writeLog(file="testApp", text="[ID: #URL.id#] request received");

sleep(5000);

writeOutput(serializeJson({"id"=URL.id, "retrieved"=now().dateTimeFormat("HH:nn:ss.lll")}));
writeLog(file="testApp", text="[ID: #URL.id#] response returned");
</cfscript>


This just takes an ID parameter and waits 5sec then returns it in a JSON packet. I wait 5sec to make the async-ness of the PHP calls easier to see. I'm also logging some stuff here to compare when the requests got to CF compared to when they were sent by PHP.

On the PHP end of things, I've got this:

use \me\adamcameron\testApp\GuzzleAdapter;
use \me\adamcameron\testApp\LoggedGuzzleAdapter;
use \me\adamcameron\testApp\LoggingService;

require_once __DIR__ . "/../vendor/autoload.php";

$endPoint = "http://cf2016.local:8516/cfml/misc/guzzleTestEndpoints/getById.cfm?id=";

$guzzleAdapter = new GuzzleAdapter($endPoint);
$logger = new LoggingService();
$adapter = new LoggedGuzzleAdapter($guzzleAdapter, $logger);

$ids = ["001", "002", "003", "004"];

$thisFile = basename(__FILE__);

$logger->logMessage(sprintf("(%s) Making requests...", $thisFile));

$responses = [];
foreach ($ids as $id){
    $logger->logMessage(sprintf("(%s) Requesting for %s", $thisFile, $id));
    $responses[] = $adapter->get($id);
}
$logger->logMessage(sprintf("(%s) Requests made", $thisFile));

$logger->logMessage(sprintf("(%s) Getting bodies from requests...", $thisFile));
foreach ($responses as $response){
    $logger->logMessage(sprintf("(%s) before calling wait()", $thisFile));
    $body = $response->wait()->getBody()->getContents();
    $logger->logMessage(sprintf("(%s) Response Body: %s", $thisFile, $body));
}
$logger->logMessage("Done");

This does the following:
  • creates the GuzzleAdapter;
  • uses a LoggedGuzzleAdapter to decorate it with some logging (the LoggingService just wraps some Monolog stuff);
  • loops over four IDs;
  • and requests their bumpf;
  • we then loop over the responses we've accumulated;
  • wait for them to resolve (calling wait blocks until they are);
  • and outputs the result;
  • all the way along, writing to the log at key points.

The actual LoggedGuzzleAdapter I'm using here is:

class LoggedGuzzleAdapter {

    private $adapter;
    private $logger;
    private $thisFile;

    public function __construct($adapter, $logger) {
        $this->adapter = $adapter;
        $this->logger = $logger;
        $this->thisFile = basename(__FILE__);
    }

    public function get($id){
        $this->logger->logMessage(sprintf("(%s) Requesting for %s", $this->thisFile, $id));

        $response = $this->adapter->get($id);

        $response->then(function($response) use ($id){
            $body = $response->getBody();
            $this->logger->logMessage(sprintf("(%s) Response for %s: %s", $this->thisFile, $id, $body));
            $body->rewind();
        });

        return $response;
    }

}


And the underlying GuzzleAdapter:

use GuzzleHttp\Client;

class GuzzleAdapter {

    private $client;
    private $endPoint;

    public function __construct($endPoint){
        $this->endPoint = $endPoint;
        $this->client = new Client();
    }

    public function get($id){
        $response = $this->client->requestAsync(
            "get",
            $this->endPoint . $id,
            ["http_errors" => true]
        );

        return $response;
    }

    public function getHandlerStack()
    {
        return $this->client->getConfig('handler');
    }

}


The things to note here are:

  • I'm logging the file name the log entry was made from. This is just to make it clearer in the logs which code is doing what for a given entry.
  • Just note the complete separation of concerns between the two files. One does logging. One does... "Guzzling". And they each focus on the job at hand, and that's it.

Here's the result. This is what PHP logged. I've trimmed-out some repetition & clutter, but have not changed any context or ordering of what got logged:

[14:15:53](makeRequests.php) Making requests...
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 001
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 002
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 003
[14:15:53](LoggedGuzzleAdapter.php) Requesting for 004
[14:15:53](makeRequests.php) Requests made
[14:15:53](makeRequests.php) Getting bodies from requests...
[14:15:53](makeRequests.php) before calling wait()
[14:15:58](LoggedGuzzleAdapter.php) Response for 003{"retrieved":"14:15:58.450","id":"003"}
[14:15:58](LoggedGuzzleAdapter.php) Response for 002{"retrieved":"14:15:58.450","id":"002"}
[14:15:58](LoggedGuzzleAdapter.php) Response for 004{"retrieved":"14:15:58.450","id":"004"}
[14:15:58](LoggedGuzzleAdapter.php) Response for 001{"retrieved":"14:15:58.450","id":"001"}
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"001"}
[14:15:58](makeRequests.php) before calling wait()
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"002"}
[14:15:58](makeRequests.php) before calling wait()
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"003"}
[14:15:58](makeRequests.php) before calling wait()
[14:15:58](makeRequests.php) Response Body {"retrieved":"14:15:58.450","id":"004"}

Things to note:

  • first and foremost: the PHP code is indeed making the HTTP requests asynchronously. Remember the CFML code pauses for 5sec for each response, which means if we do four requests that's a total of 20sec paused time. But the whole process for all four requests takes only 5sec (CF is intrinsically multi-threaded, so from its side of things, it'll process all four requests simultaneously).
  • PHP makes all four requests within the same first second of execution.
  • After 5sec, the responses start to come in.
  • The first wait call bears the brunt of this 5sec wait. Bear in mind this is a blocking operation so PHP is now waiting for the 5sec-long response from CF to come back. But after that, all the responses start to come in before their equivalent wait calls, so those promises are resolved before the code even calls wait for them.
  • most importantly: the correct logging is taking place at the correct time. So the code actually works as intended.
One interesting thing here. it doesn't seem like the act of making the request actually triggers the request. It's the act of waiting that causes the requests to be sent (albeit it: all of them).

I stuck a ten second wait in my PHP code, between making the requests and asking about their responses:

$logger->logMessage(sprintf("(%s) Requests made", $thisFile));

sleep(10);

$logger->logMessage(sprintf("(%s) Getting bodies from requests...", $thisFile));

And here's the log for that run:

[14:47:58] (makeRequests.php) Making requests...
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 001
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 002
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 003
[14:47:58] (LoggedGuzzleAdapter.php) Requesting for 004
[14:47:58] (makeRequests.php) Requests made
[14:48:08] (makeRequests.php) Getting bodies from requests...
[14:48:08] (makeRequests.php) before calling wait()
[14:48:13] (LoggedGuzzleAdapter.php) Response for 004: {"retrieved":"14:48:13.704","id":"004"}
[14:48:13] (LoggedGuzzleAdapter.php) Response for 001: {"retrieved":"14:48:13.719","id":"001"}
[14:48:13] (LoggedGuzzleAdapter.php) Response for 003: {"retrieved":"14:48:13.719","id":"003"}
[14:48:13] (LoggedGuzzleAdapter.php) Response for 002: {"retrieved":"14:48:13.704","id":"002"}
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.719","id":"001"}
[14:48:13] (makeRequests.php) before calling wait()
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.704","id":"002"}
[14:48:13] (makeRequests.php) before calling wait()
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.719","id":"003"}
[14:48:13] (makeRequests.php) before calling wait()
[14:48:13] (makeRequests.php) Response Bodies: {"retrieved":"14:48:13.704","id":"004"}
[14:48:13] Done

Notes:

  • the requests are all made @ 58.
  • you can see the 10sec sleep here. If the requests had actually been made, then they'd've all come back in that 10sec window.
  • but we can tell from the values coming back from ColdFusion that the requests weren't actually even made until 08 (as the 5sec delay is indicated in 13 in the returned timestamp
  • so it's the first call to wait that actually triggers the requests to be sent.

I'm not too sure I like that. Guzzle should crack on with it, as soon as it's asked, surely? Not wait around for me to ask "are we there yet?" Still... I don't think this will adversely impact what we're doing very often. In reality we're making a single call and then waiting for it immediately, almost all the time (this is to the extent I have actually questioned why we are doing this work, and why we're arsing about with Guzzle when we could just be using curl. But... oh well).

So that's the end of my most recent adventure with Guzzle and Promises and async calls and the like. I'm pleased with the resulting code. Not so pleased with how Guzzle works.

I've another two articles that cover the next challenge I faced with this stuff still to come. I've got the code for the second one written... but the topic for the third one only came to mind whilst writing this. So perhaps tomorrow I'll document my adventures of howTF to coerce Guzzle into raising the exceptions I choose for it to raise when requests come back with 400-500 responses, not its generic ones; and after that, how to implement a caching decorator for all this stuff. I've been off work sick for yesterday and today, but will be back in the office tomorrow. So I suspect the second article won't be out until Saturday now. I've had enough of being awake today, and am heading back to bed now. it is 3pm, after all ;-)

Righto.

--
Adam

Tuesday 4 August 2015

CFML: revision to thread syntax (revisited)

G'day:
Whilst messing around JavaScript Promises ("JavaScript: getting my brain around Promises" and "JavaScript: expectations re-adjusted re Promises") a week or so ago, I also started looking at how CFML's innate multi-threadedness, and how one would leverage this along with a CFML Promise implementation to be able to have clean & tidy & OO asynchronous code.

The general concept of firing off secondary threads in CFML is adequate, but I think the implementation leaves a bit to be desired. I'm going to forget the tag-based implementation as that is an artefact of more primitive times in CFML, and focus on the script implementation.

This is an example of lazy dull-headedness from the Adobe ColdFusion team who simply did a "tags-without-angle-brackets" implementation of <cfthread> for the script:

thread name="t1" action="run" {
    // stuff to run in its own thread here
}

This is just leaden.

A while back I mused about some improvements to the syntax ("CFML: Another suggested tweak to script: thread" and then "CFML: A possible variation on the thread idea..."). Initially I just started out suggesting changing the syntax to be a hybrid of standard OO script syntax and tags-without-angle-brackets notation:

t1 = Thread.new({someattribute="value"}) {
    // stuff to run in its own thread here
}

And then further to this:

t1 = Thread.new(required function functionToRun, optional struct dataToPass, optional struct eventHandlers);

This would result in code like this:

t1 = Thread.new(function(){
    // stuff to run in its own thread here
}, {}, {
    success = function(){
        // stuff to do after the first function finishes
    },
    failure = function(){
        // stuff to do after the first function fails
    }
});

Saturday 25 July 2015

JavaScript: expectations re-adjusted re Promises

G'day:
First off a proactive apology. I generally sit in the pub on my Saturday afternoons in Galway (every second weekend), and write some ballocks on this blog. I think we're all used to that. I'm doing that now, but prior to kicking off with the writing I have already been sitting around for two hours watching the All Blacks play the Springboks in a hard-fought, narrow-margined 20-27 victory to NZ. And this entailed drinking four pints of Guinness as the build-up and match was under way, and I'm tucking into my fifth now. So f*** knows how coherent this article will be. The up-side is that I wrote the code for this beforehand :-S

OK, so the framing for this article is the previous article: "JavaScript: getting my brain around Promises", and my admission here is that I didn't actually get my brain entirely around JavaScript promises. I set my bar slightly too high (or: just wrong) for how I'd expect JavaScript to work, as it turns out. I'm spoilt by CFML, I guess. I'll update key points of that article, but make sure to read that one first as it's still mostly all right (IMO), and this article just clarifies a coupla points I didn't quite "get" last time.

My chief misapprehension was that JavaScript Promises themselves imparted some manner of asynchronicity to code execution, but this is not the case. They are just a handling mechanism for other code which itself is asynchronous. Once I wrote some code that respected that, things made more sense to me.

Here are some examples of how Promises help streamlining async code.