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