Clojure from zero to hero (part 3) - First endpoint

Jul 8, 2019

Integrated Development Environment (IDE)

Using an IDE for Clojure development raises controversy a few times. In the book Clojure for the Brave and True, Emacs and Cider are the recommended tools. Other folks use IntelliJ IDEA and Coursive plugin or Visual Studio Code and Calva plugin or VIM and a few other plugins. I use SpaceMacs (Emacs with VIM keyboard shortcuts), Cider and Clojure layer. The setup is better explained by Practicalli. Please go ahead and pick whichever environment you feel the most comfortable with. I chose SpaceMacs because Emacs works realy well integrated with Cider and I am familiar with VIM’s keyboard shortcuts. Learning a new programming language (Clojure), a new programming paradigm (functional programming) and a new IDE (Emacs) is a very steep learning curve. I chose to flatten a little bit by using SpaceMacs.

Our first custom endpoint

In the web application that we created in episode 0 we will start adding a custom endpoint. For now, all we want is to have a resource that returns in JSON format every time when we go to /temperature. This resource we will hook it later with a sensor that will read temperature and humidity in a particular location.

For now we need not to worry about security, we need to write a bit of Clojure.

If you navigate to the root directory of your Pedestal application and list all the directories, you should get something similar with the image bellow. The directory we will focus at the moment is test. Before writing any code, we will write a failing test. Test Driven Development TDD is a rapid cycle of testing, coding, and code improvement.

image

From when we created the template Pedestal app, we already have a sample test file. If you run it in your favourite IDE, it should pass.

➜  pedestal-api lein test

lein test pedestal-api.service-test
INFO  io.pedestal.http  - {:msg "GET /", :line 80}
INFO  io.pedestal.http  - {:msg "GET /", :line 80}
INFO  io.pedestal.http  - {:msg "GET /about", :line 80}
INFO  io.pedestal.http  - {:msg "GET /about", :line 80}

Ran 2 tests containing 4 assertions.
0 failures, 0 errors.
➜  pedestal-api

TDD step 1: Red (writing a failing test)

Our new resource should respond with JSON format at any GET requests that reaches /temperature. Let’s go ahead and edit the file in test/pedestal_api/service_test.cls and let’s add a new test. We can call it temperature-page-test. For now we just assert that we get a JSON object in the body when we do a GET request to /temperature and that content type from the server is application/json. The body should return a hardcoded zero Celsius and 32 Fahrenheit for now.

(deftest temperature-page-test
  (is (=
       (:body (response-for service :get "/temperature"))
       "{\"celsius\":0,\"fahrenheit\":32}"))
  (is (= "application/json;charset=UTF-8"
        (get-in (response-for service :get "/temperature") [:headers "Content-Type"]))))

For the above assertion to work we need to import [clojure.string :as s]. It was added in Clojure 1.8.

Running the above test should return two errors, for both the body and the headers blocks. This is the first step in a TDD cycle, the red part, where we have a failing test, signalled by the colour red in most testing frameworks.

TDD step 2: Green (implementing the code and making the tests pass)

In order to make the tests pass, we need to write some code. First stop is to write a handler function that will be able to get a request object as parameter and return the payload we expect. Navigate to src/pedestal_api/service.clj and create one new function:

(defn temperature-page
  [request]
  (ring-resp/response {:celsius 0
                       :fahrenheit 32}))

Then we run our tests but they still fail. Is is because we didn’t tell to the router about our new resource. Next step is to add our new handler function for route /temperature to routes. Something along the lines of:

["/temperature" :get (conj [(body-params/body-params) http/json-body] `temperature-page)]

Next step is to run the new tests. I use SpaceMacs and Clojure layer, but it can be run from command line using lein test and the result should look like

Ran 3 tests containing 6 assertions.
0 failures, 0 errors.

Given that all our tests are passing we are free to move to the next step of TDD.

TDD step 3: Refactor (improve code without breaking tests)

This step is about code improvements. Many times we hack something to make the test(s) pass and is not all the times the best solution. After making all the tests pass, we can go back and improve our code by renaming functions and data, extracting code into smaller units, moving code around into better named names spaces.

Before starting changing code, we need to add our changes to git. In case we do too many changes and don’t keep track of them, we have a clean slate to start over our improvements.

➜  pedestal-api git:(master) ✗ git status

modified:   src/pedestal_api/service.clj
modified:   test/pedestal_api/service_test.clj

➜  pedestal-api git:(master) ✗ git add .
➜  pedestal-api git:(master) ✗ git commit -m "Added a new endpoint /temperature that returns hardcoded values as JSON"

Now that we have our code versioned locally we can start improving it. Given that we use a vector to do the conjoin operation, it looks like a good candidate to be extracted into a def. For now we can call the new def common-json-interceptors to be similar with the one called common-interceptors. This def needs to be declared before the routes such that it can be used.

;; returns a JSON response encoding any map sent to it as valid
;; JSON object into the body and adds the correct headers
(def common-json-interceptors [(body-params/body-params) http/json-body])

And the route now can use the new def:

["/temperature" :get (conj common-json-interceptors `temperature-page)]

Given that we have tests to cover our code, we are confident that we didn’t brake our code by making these changes. Running the tests again, should yeld the same result as above:

➜  pedestal-api git:(master) ✗ lein test

lein test pedestal-api.service-test
INFO  io.pedestal.http  - {:msg "GET /", :line 80}
INFO  io.pedestal.http  - {:msg "GET /", :line 80}
INFO  io.pedestal.http  - {:msg "GET /about", :line 80}
INFO  io.pedestal.http  - {:msg "GET /about", :line 80}
INFO  io.pedestal.http  - {:msg "GET /temperature", :line 80}
INFO  io.pedestal.http  - {:msg "GET /temperature", :line 80}

Ran 3 tests containing 6 assertions.
0 failures, 0 errors.

Once we finish improving our code, amend the previous commit, add a more descriptive “why” message (if needed) and push to master.

Looking forward to the next section.

Tags: programmingclojure

Archives

  1. February 2024
  2. Maximizing Software Development Productivity: The Power of Flow and Minimizing Interruptions
  3. December 2023
  4. Clean Code in Java: Writing Code that Speaks
  5. Clean Code in Java: A concise guide
  6. Understanding Value Objects in Java: A Brief Guide
  7. August 2023
  8. Consuming RabbitMQ Messages with Clojure: A Step-by-Step Tutorial with Tests
  9. January 2023
  10. Running a Spring Boot service with kubernetes
  11. December 2022
  12. Hosting a PWA with Jekyll and Github pages
  13. November 2022
  14. Global Day of Code Retreat
  15. Facilitating a mini Code Retreat
  16. October 2022
  17. The Curse of Optional
  18. September 2022
  19. Testing Spring Boot Microservices - Presentation
  20. March 2022
  21. TDD Workshop
  22. February 2022
  23. Value Objects in Java
  24. Efficient Java
  25. January 2022
  26. Spring Boot testing - Focus on your changes
  27. Product users - Personas
  28. December 2021
  29. Write code fit for testing
  30. November 2020
  31. Running a Spring Boot app with kubernetes
  32. September 2019
  33. Setup GPG on Mac and sign git repositories
  34. July 2019
  35. Running a Clojure Pedestal application on Raspberry Pi model B revision 2
  36. Clojure from zero to hero (part 3) - First endpoint
  37. Clojure from zero to hero (part 2) - A bit of syntax
  38. June 2019
  39. Clojure from zero to hero (1) - explaining project.clj
  40. Clojure from zero to hero (0) - creating a Pedestal app
  41. November 2017
  42. Introduction to Docker
  43. April 2015
  44. Git micro commits
  45. July 2014
  46. Google Glass Development - setup tools, environment and turn on debugging on Glass
  47. June 2013
  48. How To: Get the rendered HTML of a webpage with Python
  49. Set union of two lists in Python