Wednesday, May 13, 2020

F# - Refactoring to a Functional Design - Part 1

Shortly after the coronavirus pandemic started, I started trying to create web-based tools for my wife (who is a preschool teacher) to evaluate the progress her students are making with distance learning. I reached for Azure Functions first since they are quick for implementing a publicly visible API. As I created and worked with this new API, I needed a way to test the end points to make sure they worked the way I needed them to work. I decided to create a tool in F# to do this testing.

(I know tools like Postman exist and are great. As I was working on the application in question, I was working on a brand new laptop, and I didn't want to go nuts installing software. Also, as much as I like Postman, it has a few drawbacks. The most notable two for me are the fact that changes to tests don't appear in a text file until the user exports the tests. This disconnect results in me forgetting to update tests from Postman with my source code. The other drawback is that additional third-party tools are needed to run Postman tests from some popular CI/CD tools. I recently ran into an issue with one of these tools where the wrapper for the CI/CD pipeline was incompatible with an NPM module needed to run Postman tests. This incompatibility forced me to remove the tests from my build pipeline. That shouldn't happen.)

My first pass at the code in F# is like what I have below: This code is alright, but you can see that there is a lot of duplication for each endpoint to call. This code is also more difficult to read and maintain. I would like an easy way to define an API endpoint to exercise, and just have it tested.

The first refactoring I will do is to remove the duplication of the HTTP call. In the code above, the HTTP calls are not terribly onerous. These calls are a single line of code for each endpoint, and that's not bad. What I don't like about this code is that the HTTP dependencies are mixed with the rest of my tests, and I have to include the HTTP method calls with my definition of the end point to test.

The first step for me was to create an abstraction for the endpoint itself. I want to use an F# type for the endpoint, but I don't want this type to be a class or to contain any logic. To achieve this purpose, I used the F# struct type. This element allows me to define value fields in my type, along with a constructor. My Endpoint struct is below: This "type" gives me a Name so that I can identify my test externally, a URL to hit, the HTTP verb to use in the call, and a "body" for sending in data when necessary. (For this post, I won't be using the Body property.)

Now that I have an endpoint abstraction in place, I need a method to make my HTTP call. Since this is a single line of code in my original file, it should be a pretty short and straightforward method. The method looks like:

let GetEndpoint (endPoint: Endpoint) =
    Http.Request(endPoint.Url, httpMethod = endPoint.Verb)
This method takes in an endpoint. I wanted to ensure that this method stuck to my endpoint abstraction, so I specified a type with the ": type" syntax. Since every line of code in F# is an expression, this method returns the result of the Http.Request method, which is an HttpResponse object.

With my endpoint abstraction and my function to make HTTP calls, I can modify my original testing code to look like this:

This code doesn't look a lot better than the original code, but it is a little easier to see the endpoints that are being tested. This code also shows me that I still have a good bit of "boilerplate" stuff with my testing of the response data. The testing code will be the subject of my next post.

No comments:

Post a Comment