Backend modules

This page describes the base structure and practices to use for modules in tzbackend.

Domain type modules

Domain type modules should be implemented in accordance with the guidelines listed below.

Structure

/src/tzbackend/domain_types
└── my_type/
    ├── api/
    ├── mchan/
    ├── integration_tests/
    ├── routes.clj
    ├── mchan.clj
    ├── scheduler.clj
    └── core.clj

api/

  • Each file should include a single public function
  • Try to apply single responsibility rule; separate tasks into smaller private functions
  • Each function in the file should be unit tested
  • Filename should correspond to the public function’s name

integration_tests/
To be evaluated.
Include domain specific integration tests in the integration_tests directory. Try to find naming convention for test files, a name that reflects the action being performed.

routes.clj
Should contain route definitions.
See guidelines for handlers below.

mchan.clj
Created if needed. Should contain mongo channel event handlers.
See guidelines for handlers below.

scheduler.clj
Created if needed. Should contain jobs/workers related to the domain type.
See guidelines for handlers below.

core.clj
Should only contain the domain type definition.

Handlers (request, scheduler, and mchan)

A request-, scheduler-, mchan-handler should only be concerned with what it should do and not how it is done. Implementations of handlers should be kept in seperate functions.

Consider the following example where the behavior of the handler where the validation and entity update is done inline:

(defn- handle-update-type [params]
  (ability/authorize! :update :my-type params)
  (when-not @(p/select my-type {:id params})
      (throw+ {:code 404
               :message "Request type does not exist"}))
  (when (= (:my-field params) "forbidden-value")
      (throw+ {:code 400
               :message ":my-field cannot include forbidden-value"}))
  (mongo/update! (dom/collection-name my-type)
                 {:_name (:id params)}
                 :multiple? false
                 :upsert? true)
  {:status 201})

(defroutes http-routes
  (context "/my-types/v1" []
    (PUT "/my-types/:id" req (handle-update-type (:params req)))

The validation should be broken into its own function (validate-my-type!) and the update broken into function (update-my-type!), leaving the handler to only describe what should happen when handler is triggered, and not how it is being done.

(defn- validate-my-type! [params]
  (when-not @(p/select my-type {:id params})
      (throw+ {:code 404
               :message "Request type does not exist"}))
  (when (= (:my-field params) "forbidden-value")
      (throw+ {:code 400
               :message ":my-field cannot include forbidden-value"})))

(defn- update-my-type! [params]
  (mongo/update! (dom/collection-name my-type)
                 {:_name (:id params)}
                 :multiple? false
                 :upsert? true))

(defn- handle-update-type [params]
  (ability/authorize! :update :my-type params)
  (validate-my-type! params)
  (update-my-type! params)
  {:status 201})

(defroutes http-routes
  (context "/my-types/v1" []
    (PUT "/my-types/:id" req (handle-update-type (:params req)))