aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: 95c842c0a3ee27b23fbdcccdc7f4ff52e4ecbdd0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# queue-api

A simple job queue api

## Usage

To run just type

`lein run`

then access [localhost:3000/swagger-ui](http://localhost:3000/swagger-ui/index.html).

## Stack

I chose [luminus](http://www.luminusweb.net/) for my stack as it makes the initial setup way easier since it provide a wide range option of configuration for a bunch of technologies.
To bootstrap the project I used `lein new luminus queue-api +swagger +service +kibit` plus datascrypt which doesn't come with Luminus.

### +Swagger

When possible I always add it to a project for it make easier to visualize and test endpoints.

### +Service

To remove all front end stuff since I didn't need it.

### +Kibit

I added because it gives some insight how to make you code more idiomatic.

### Datascript

I chose [datascript](https://github.com/tonsky/datascript) for its easy setup, after little to no effort I had it working.
Even though it was meant to run on browser it fit nicely in the project, and because it works pretty much like Datomic it has [powerful query system](https://docs.datomic.com/on-prem/query.html) and works seamlessly with clojure.
Additionally it had an okay [non-documentation](https://github.com/tonsky/datascript/wiki/Getting-started) with some [samples](https://github.com/kristianmandrup/datascript-tutorial) and if I couldn't find for Datascript I'd search for a Datomic seeing that query system of both are compatible.  

## Solution

### Data structure

Project has two models:

* Agent
    * `:agent/id` unique identification of an agent
    * `:agent/name` agent name
    * `:agent/primary-skillset`: list of primary skillsets of an agent
    * `:agent/secondary-skillset` list of secondary skillsets of an agent
    * `:agent/job` reference to job that the agent is processing
* Job
    * `:job/id` unique identification of a job
    * `:job/status` current status of the job it can be:
        * `:unassigned` it is waiting to be assigned
        * `:processing` it is being precessed by an agent
        * `:completed` it has been done with.
    * `:job/agent` reference a job that is processing this job or had processed it.
    * `:job/type` type of the job that it can perform
    * `:job/date` date time for when job has entered the system.
    * `:job/urgent` urgent flag that tell when a job has higher priority.
    
Those models wrap up in schema:

```clojure
{:agent/id                 {:db/unique :db.unique/identity}
 :agent/primary-skillset   {:db/cardinality :db.cardinality/many}
 :agent/secondary-skillset {:db/cardinality :db.cardinality/many}
 :agent/job                {:db.valueType :db.type/ref}
 :job/id                   {:db/unique :db.unique/identity}
 :job/agent                {:db.valueType :db.type/ref}}
``` 

### services.clj

After all luminus file there is actually two files that have the core logic of the app `services.clj` and `db/core.cljs`.
For `services.clj` it holds all code for endpoint definition and model validation. considering the exercise requirements we gonna need 5 endpoints:

* Endpoint to add agent is  a`:put` at `/agent`  
* Endpoint to get how many jobs of each type this agent has performed is a `:post` at `/agent`. Note: usually since it is a method that doesn't modify anything I'd have used `:get` and pass the agent id via path (`/agent/:id`) but one of the requirement is "*All endpoints should accept and return JSON content type payloads*" I worked with POST PUT.
* Endpoint to add a job is `:put` at `/job`
* Endpoint to request a job is `:post` at `/job`
* Endpoint to get current queue state is `:get` at `/job`

For model and validation details access swagger-ui.

### db/core.clj

Core.clj holds all logic to interact with Datascrip therefore all the code to manage the queue.
The idea behind it is actually simpler than part 1 since Datascrip handle the hard word. 
For example, to store jobs and agents I'd simply `transact!` the entire object and we good to go.

```clojure
 (d/transact! queue-api.db.core/conn
               [{:job/id     "319ce5e6-6ed6-4931-8e29-550052b02409"
                 :job/type   "bills-request"
                 :job/urgent false
                 :job/date   (time/now)
                 :job/status :unassigned}])
```

While with a simple query I can fetch the exact piece of information that I need for the moment.

```clojure
(d/q '[:find ?d ?id
      :where
      [?e :job/date ?d]
      [?e :job/id ?id]
      [?e :job/urgent false]
      [?e :job/status :unassigned]
      [?e :job/type ?t]
      [(clojure.string/includes? ["bills_request" "rewords_request"] ?t)]]
  @conn)
```

## Testing

Every test case start with pristine database then set up all data needed to test all possible (at least all I could think of)  combinations, once test is done it does it all over again.

```clojure
(use-fixtures
  :each
  (fn [f]
    (mount/stop #'queue-api.db.core/conn)
    (mount/start #'queue-api.db.core/conn)
    (d/transact! conn base-schema)
    (f)))
```

Usually there are problems with this approach, it get slower as the system grows, some times you don't the luxury of starting with clean database, or you would require too much data to exist in the database in order to run tests that would become a mess.
Fortunately this app does not check any of those boxes since it works with in-memory database and it has a very small set of models.