Wednesday, August 29, 2018

Outside-In TDD the bank kata in Clojure/ClojureScript


Code: https://github.com/DanBunea/katas/tree/master/outside-in%20tdd%20bank%20kata 


In "London school" TDD or Outside-In we start from an acceptance test, then write unit tests. This is better explained as the double loop of TDD:




Some time ago, I found this great step by step explanation of Outside-In TDD, by Sandro Mancuso from Codurance:



However it was all Java and obviously OOP, so I wanted do the same thing but in Clojure, and fully functional: functions and not classes. So here we go:





Restrictions (adapted for functional):

1. Start with a module account that has the following functions:

(defn deposit [account amount] ...)
(defn withdraw [account amount] ...)
(defn print-statement [account] ...)

Threat the methods, as if they cannot return any values!

2. You're not allowed to add any new functions to the module
3. Strings and integeres are to be used for dates and amounts for simplicity
4. Don't worry about spacing in the statement printed at the console


Step 1: The acceptance test (the big loop)





we need to take this test, unit it fails for the right reason: the results of the console-print are not what was expected, so we write two modules:



and account.cljs:



and now the test:



Step 2: The first unit test - account module - the business layer (the small loop)


The very first module we should test is account, so we make account_tests. For account, we could test the deposit function. Considering we might have a module which will actually do the depositing/withdrawals in a database or something, that module will be separate and we'll call it transaction-repository. So we could test the interactions between modules like:



We expect that when we invoke deposit from the account module, the add-deposit from the transaction-repository will be invoked.



we make it pass,



and continue with withdraw and later with print-statement for which we'll do a separate module: statement-printer. The final test will be:



and:



and:




Step 3: the unit tests for the "data layer" (the small loop)

Like for the account, we start with a simple test, where we make a deposit and check if all-transactions will return it. We also need to make sure it's on a certain date. We make the test pass, and we move on to withdraw, ending up with:



and:



and:




Step 4: Unit testing the statement-printer (the small loop)


Now, we'll go straight to the results, even though I used TDD to get to it:

The tests:




The code obtained:



The tests result:



Let's not forget that our console-print function is still mocked, and the implementation is:



and we can now implement it:



Tests still pass.

Step 5: Going back to the acceptance test (the big loop)


We will modify the test, mocking the dates delivered, as well as console-print:




And when we run all the tests:




Conclusion


Starting from the outside, we could test-drive the design and the code.

acceptance-test -> 
      account-tests->account->
      transaction-respository-tests->transaction-repository->
      statement-printer-tests->statement-printer->console


Monday, July 02, 2018

Simple , robust code: part one, simplicity

1. Simple as the oposite of complex


Complexity in software is the root of all evil, and simplicity is the oposite of complexity. Simple is not the same as easy, because sometimes we make software complex just because it is easy (think of adding a library from which you need just a function, which then needs to be upgraded and it's incompatible with other libraries etc).

A complex sistem is like this, where is is very hard to figure out what is going on, thus it cannot be debugged, extended or changed:




and a simple one is the oposite:



2. Simplicity in software DATA and FUNCTIONS*

We use computers to compute (apply functions) some data we need (the final state of a system), given some initial data (initial state of the system). So if we drastically reduce what software does, we end up with just data and functions.





Example:




Obviously this sounds overly simplistic, real code is more complex, more functions are needed.

function greed(name){
var a = ["hello ", name];
var b =capitalize_first_letter(a);
var c =concat(b);
return c;
}


or we could:





Which starts to look like a pipe, where you send the initial_state, and expect at the end the final state.

Now if we need to solve a real world problem, I guess we could solve it by having:
- lots of simple functions, that take as input one parameter and return one parameter
- because the have one parameter in and one parameter our they can be composed
- simple functions put together as a pipeline and can solve very complex problems in a very simple way

3. Functional composition


Now we could compose the two functions into just one:



4. Example: From complex to simple using functional composition


A few years ago, I made a practical example. I'll add it simplified here.

Requirement: in the json that we receive on a server, we need to have a key “measurement”, that is mandatory, cannot be null, needs to be a string and cannot be empty string, Then we also need to make sure the length of the string is between 3 and 8 characters, and cannot be some reserved words like “password” or “archived". So the code is like:



 def validate_simplest_json(json):  
   errors = []  
   if not json.has_key("measurement"):  
     errors.append("measurement cannot be missing")  
   else:  
     if json["measurement"]==None:  
       errors.append("measurement cannot be null")  
     else:  
       if not isinstance(json["measurement"], str) and not isinstance(json["measurement"], unicode):  
         errors.append("measurement needs to string or unicode")  
       else:  
         lenm=len(json["measurement"].strip())  
         if lenm==0:  
           errors.append("measurement cannot be an empty string")  
         else:  
           if lenm<3: data-blogger-escaped-div="">  
             errors.append("measurement needs at least 3 characters")  
           elif lenm&gt;10:  
             errors.append("measurement needs at most 10 characters")  
           elif json["measurement"].strip().lower() in ["archived","password"]:  
             errors.append("measurement has a value which is not allowed")  
   return errors  


Removing complexity can mean, more linear code, and an initial state, and simple composable functions:

 ValidationState = namedtuple("ValidationState","json key errors exit”)  

then I will extract the actual validations in simple functions, like:

 def validate_simplest_json_imperative_linear_with_state(json):  
   initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)  
   
   state = validate_key_exists(initial_state)  
   
   if not state.exit:  
     state = validate_not_null(state)  
   
   if not state.exit:  
     state = validate_string_or_unicode(state)  
   
   if not state.exit:  
     state = validate_not_empty_string(state)  
   
   if not state.exit:  
     state = validate_length(state, 3,10)  
   
   if not state.exit:  
     state = validate_not_in(state, ["archived","password"])  
   
   return state.errors  

And the functions are like:

 def validate_key_exists(state):  
   print validate_key_exists.__name__,state  
   if not key_exists(state.json,state.key):  
     return state._replace(errors = state.errors+["{0} cannot be missing".format(state.key)])._replace(exit=True)  
   return state  
   
 def validate_not_null(state):  
   print validate_not_null.__name__,state  
   if value_null(state.json,state.key):  
     return state._replace(errors = state.errors+["{0} cannot be null".format(state.key)])._replace(exit=True)  
   return state  
   
...


The code looks is now a series of functions that run with the result of the previous function if the exit parameter is not set to True. So basically having 2 functions f,g they’ll be composed like:

initial_state = …
state = f(initial_state)
if not state.exit:
    return g(state)

And putting this in a function:

 def compose2(f, g):  
   def run(x):  
     result_f = f(x)  
     if not result_f.exit:  
       return g(result_f)  
     else:  
       return result_f  
   return run  

 #compose n functions  
 def compose(*functions):  
   return reduce(compose2, functions)  

And now the final validation code:

 def validate_simplest_functional_composition(json):  
   initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)  
   
   composed_function = compose(
          validate_key_exists
          validate_not_null,
          validate_string_or_unicode,
          validate_not_empty_string
          create_validate_length(3, 10), 
          create_validate_not_in(["archived","password"]))  
   final_state = composed_function(initial_state)  
   
   return final_state.errors  

It is much better. It basically says: having an initial start of the system, run all these functions (validators) and at the end get a final state. Code: http://runnable.com/VNMhoTKLSn9Tm0GI/fighting-complexity-through-functional-composition-for-python


And it is:




5. So where can I use this?


If you're a backend developer, you can use it on a server (python example):

@mod.route('/api/1/save/', methods=['POST'])
@pi_service()
def generic_save(version=1, typ=None):
    composed_func = compose_list(
    [
        can_write("tags"),
        change("json", request.json),
        change("session", get_session()),
        change("type", get_pi_type(typ)),
        change("object", None),
        change("transformer", get_pi_transformer(typ)),
        get_database_object,
        transform_from_json,
        save_database_object,
        index_tag_or_tag_group,
        pi_transform_to_json,
   ])
return composed_func({})

or in a PDF generating server, written in Clojure over Apache Batik (using transducers but that's another discussion)



You could use javascript promises for piping, with React, if you're a front-end developer. The state of the system the model (immutable) and rendering is done views.render:

StoryboardController.prototype.move_point_by = function(page_object, point_index, dx, dy) {
    pi.startWith(model,"MOVE POINT BY")
        .then(function move_point_by(state){
            pi.info("move point by", page_object, point_index, dx, dy);
            var cursor = get_selected_layer_cursor(state) + ".children" + find_cursor_pageobject(page_object, state);
            if (cursor) {
                var point_cursor = cursor+".points["+point_index+"]";
                var point = pi.pi_value(state, point_cursor);
                var changes = {};
                var nx=point.x+dx;
                var ny=point.y+dy;
                changes[point_cursor+".x"]=nx;
                changes[point_cursor+".y"]=ny;

                state = pi.pi_change_multi(state, changes);

                return resize_shape(state, cursor);
            }
            return state;
        })
        .then(views.render)
        .then(swap_model)
        .then(REST.try_save_page)
}

or


 
or in Clojurescript, where the state of the system is an atom (model) and every time it changes, the view is rerendered:




6. Conclusion


Using this model, code is easier to understand, debug, change, extend. Why: 
- all the data is in a place

initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False) 

- functions are simple 

 def validate_key_exists(state):  
   print validate_key_exists.__name__,state  
   if not key_exists(state.json,state.key):  
     return state._replace(errors = state.errors+["{0} cannot be missing".format(state.key)])._replace(exit=True)  
   return state 

- intermediary states can be easily debugged

 composed_function = compose(
          validate_key_exists
          validate_not_null,
          debug,
          validate_string_or_unicode,
          validate_not_empty_string
          create_validate_length(3, 10), 
          create_validate_not_in(["archived","password"]))  
   final_state = composed_function(initial_state)  


 def debug(state):  
   print state.json, state.key, state.errors, state.exit
   return state 

- data changes flow in a single direction

In part two: robustness, we'll see how we could also make the code robust, by making the code run transactionally same as databases: either all runs or none and the state gets reverted to the previous one. 








Thursday, April 13, 2017

What is wrong with static typing in JavaScript and how clojure.spec solves the problem (Part 2)

The problem


The main problem with dynamic typing seems to be fear that the wrong type of data will end up in the wrong place (function input for instance).

However it turns out static typing is pretty useless.

Example 1: Simple types Age


Let's say you have to record an age for a person in a variable, or have it as a parameter in a function.

You would do something like:

int age = 25;

or

function something(int age...)

Someone pretended even that static typing shows intent. Now the only thing that is in here, is that it will be an int. There is nothing protecting the age from being either negative (-2) or too big (4000, it might work if you're talking about the age of the pyramids). So it is not intent, it is just int, not further protection, so pretty much useless.

Solution 1

In Clojure REPL using spec (require '[clojure.spec :as s]) we define a spec saying we want a natural int (positive int) and it should be smaller then let's say 150:

(s/def ::age (s/and nat-int? (fn [x] (< x 150))))

Now:

user=> (s/valid? ::age "a")
false
user=> (s/valid? ::age -12)
false
user=> (s/valid? ::age true)
false
user=> (s/valid? ::age 1.21)
false
user=> (s/valid? ::age 4000)
false
user=> (s/valid? ::age -2)
false
user=> (s/valid? ::age 0)
true
user=> (s/valid? ::age 12)
true
user=> (s/valid? ::age 29)
true
user=> (s/valid? ::age 99)

true


Example 2: Composed types: Person


Let's say we get through a web call a json like:

{
  "id":6,
  "name":"Dan",
  "age":28
}

Usually people would create a class

class Person
{
    int id;
    string name;
    int age;
}

So we have the same problems, for instance name might be null, or age might be negative.

Then in modern apps, you get json and you send json, so you need to be able to serialize and deserialize to json this class. What happens if one of the parameters is not comform or missing?

Solution 2

(s/def ::id nat-int?)
(s/def ::name string?)
(s/def ::person (s/keys :req-un [::id ::name ::age]))

Now:

user=> (s/valid? ::person {:id 1, :name "Adi"})
false
user=> (s/valid? ::person {:id 1, :name "Adi" :age -1})
false
user=> (s/valid? ::person {:id 1, :name "Adi" :age 40})
true

What's even cooler, is that if it isn't valid, you can get an explanation:

user=> (s/explain ::person {:id 1, :name "Adi" :age -1})
In: [:age] val: -1 fails spec: :user/age at: [:age] predicate: nat-int?

user=> (s/explain ::person {:id 1})
val: {:id 1} fails spec: :user/person predicate: (contains? % :name)
val: {:id 1} fails spec: :user/person predicate: (contains? % :age)

Example 3: Hierarchies 


What if you have:

{
    "id": 6,
    "name": "Dan",
    "age": 28,
    "children": [{
            "id": 7,
            "name": "Alex",
            "age": 5
        }
    ]
}

When the first object is a parent, in a school and he must have at least one child? You can enforce the relationship by writing a function and in the constructor, but then you also need to change the serialization/deserialization from json to enforce the rules, and of course you will write more code and you will forget to check it once, and there will be a bug.

And the most common problem of our times. The json is like:

{
    "id": 11,
    "name": "Maria",
    "age": 95,
    "children": [{
                "id": 5,
                "name": "Elena",
                "age": 28,
                "children": [{
                    "id": 6,
                    "name": "Dan",
                    "age": 60,
                    "children": [{
                        "id": 7,
                        "name": "Alex",
                        "age": 5
                    }],
                    {
                        "id": 9,
                        "name": "Alina",
                        "age": 32,
                        "children": [{
                            "id": 121,
                            "name": "Luiza",
                            "age": 0
                        }]
                    }
                }],

                {
                    "id": 23,
                    "name": "Petru",
                    "age": 70,
                    "children": [{
                            "id": 4,
                            "name": "Adrian",
                            "children": [{
                                "id": 45,
                                "name": "Denis",
                                "age": 12
                            }],
                        ]
                    }]
            }]
}

You have a single error but where? (Maria / Petru / Adrian - missing "age"). It is not only hard to validate it but it is hard to show explicitly where the error occurred.

Solutions

(s/+ says that there will be a collection of person's with a minimum of 1:

(s/def ::children (s/+ ::person))
(s/def ::parent (s/keys :req-un [::id ::name ::age ::children]))

Now:

user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children []})
false
user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 40}]})
true
user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 40}, {:id 2, :name "Dan" :age 20}]})
true

and if we don't have children:

(s/explain ::parent {:id 1, :name "Adi" :age 40 :children []})
In: [:children] val: () fails spec: :user/person at: [:children] predicate: :user/person,  Insufficient input


Even cooler is that you can check relations between data, like if the children are younger then their parents: 

(defn parent-older-than-children? [parent] (reduce #(or %1 %2) (map #(> (:age parent) (:age %)) (:children parent))))

we redefine the ::parent

(s/def ::parent (s/and (s/keys :req-un [::id ::name ::age ::children]) parent-older-than-children?))

user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 50}]})
false
user=> (s/explain ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 50}]})
val: {:id 1, :name "Adi", :age 40, :children [{:id 1, :name "Adi", :age 50}]} fails spec: :user/parent predicate: parent-older-than-children?

user=> (s/valid? ::parent {:id 1, :name "Adi" :age 45 :children [{:id 1, :name "Adi" :age 25}, {:id 2, :name "Dan" :age 20}]})
true


In Part 3 we will look at functions and one more thing ...

Thursday, March 30, 2017

What is wrong with static typing in JavaScript and how clojure.spec solves the problem (Part 1)

The problem


The main problem with dynamic typing seems to be fear that the wrong type of data will end up in the wrong place (function input for instance).

However it turns out static typing is pretty useless.

Example 1: Simple types Age


Let's say you have to record an age for a person in a variable, or have it as a parameter in a function.

You would do something like:

int age = 25;

or

function something(int age...)

Someone pretended even that static typing shows intent. Now the only thing that is in here, is that it will be an int. There is nothing protecting the age from being either negative (-2) or too big (4000, it might work if you're talking about the age of the pyramids). So it is not intent, it is just int, not further protection, so pretty much useless.

Example 2: Composed types: Person


Let's say we get through a web call a json like:

{
  "id":6,
  "name":"Dan",
  "age":28
}

Usually people would create a class

class Person
{
    int id;
    string name;
    int age;
}

So we have the same problems, for instance name might be null, or age might be negative.

Then in modern apps, you get json and you send json, so you need to be able to serialize and deserialize to json this class. What happens if one of the parameters is not comform or missing?

Example 3: Hierarchies 


What if you have:

{
    "id": 6,
    "name": "Dan",
    "age": 28,
    "children": [{
            "id": 7,
            "name": "Alex",
            "age": 5
        }
    ]
}

When the first object is a parent, in a school and he must have at least one child? You can enforce the relationship by writing a function and in the constructor, but then you also need to change the serialization/deserialization from json to enforce the rules, and of course you will write more code and you will forget to check it once, and there will be a bug.

And the most common problem of our times. The json is like:

{
    "id": 11,
    "name": "Maria",
    "age": 95,
    "children": [{
                "id": 5,
                "name": "Elena",
                "age": 28,
                "children": [{
                    "id": 6,
                    "name": "Dan",
                    "age": 60,
                    "children": [{
                        "id": 7,
                        "name": "Alex",
                        "age": 5
                    }],
                    {
                        "id": 9,
                        "name": "Alina",
                        "age": 32,
                        "children": [{
                            "id": 121,
                            "name": "Luiza",
                            "age": 0
                        }]
                    }
                }],

                {
                    "id": 23,
                    "name": "Petru",
                    "age": 70,
                    "children": [{
                            "id": 4,
                            "name": "Adrian",
                            "children": [{
                                "id": 45,
                                "name": "Denis",
                                "age": 12
                            }],
                        ]
                    }]
            }]
}

You have a single error but where? (Maria / Petru / Adrian - missing "age"). It is not only hard to validate it but it is hard to show explicitly where the error occurred.

Example 5: Functions


Let's try to find a string in another string. A function would be like:

int indexOf(string search, string what) ...

Which tells you that you will get an int, and you can pass two strings. First what if the strings are null? What if both strings are empty "", "". What if the result for "ab", "b" is 1248764 or -12. According to the function definition it is an int, and should be valid.

Example 6. Unit testing


To ensure the function above is well specified, we also use unit testing. Problem 1: unit testing doesn't care if it is static or dynamic typing. Problem 2 is very unit testing specific: Having enough tests, maintaining them when the function changes (like adding a new parameter), not enough testing, or too optimistic testing.


The solution proposed by Clojure.spec will be shown in part 2. And it is pretty cool! :)

Friday, March 17, 2017

Sorting maps in Clojure

Problem 

If you like to keep data in maps in ClojureScript to be able to access it fast, but also need sorting, maybe you should read this.

 Cause

 Let's say you have a map like:

 (def a {:0 0, :1 1, :2 2, :3 3, :4 4, :5 5, :6 6, :7 7}) 
 (vals a) would return: (0 1 2 3 4 5 6 7) 

 But what about

 (def a {:0 0, :1 1, :2 2, :3 3, :4 4, :5 5, :6 6, :7 7, :8 8}) 

 where

 (vals b) returns: (6 7 4 5 1 0 3 2 8) 

 The trick is how data is represented internally. If the number of pairs is less then 8 then (type a) is a clojure.lang.PersistentArrayMap but (type b) is a clojure.lang.PersistentHashMap which is optimized for access, but loses order as a compromise.

 If we generate our maps using a function:

 (defn gen [x] (doall (map (fn [x] [(keyword (str x)) x]) (range x)))) try: 

 (->> (gen 8) 
         (into {}) 
          type ) 

 (->> (gen 9) 
         (into {}) 
          type ) 

and you'll see for yourself.

Wednesday, October 19, 2016

Learn clojure.spec (cljs,spec) interactively

There is one fantastic tool called klipse which allows you to run Clojurescript interactively in the browser. It is even more helpful when it allows you to learn something like clojure.spec by examples that actually run in realtime and which you can change and see how everything works: http://blog.klipse.tech/clojure/2016/05/30/spec.html

Friday, December 18, 2015

Clojurescript/Reagent: How to start in 1 minute


What is a great development environment for web applications?


One that:

  1.  can be started and configured extremely easy
  2.  allows instant feedback
  3.  can be done in a great programming language  
  4.  doesn't need expensive tools


So Clojurescript. For clojure/clojurescript projects you need one tool installed: Leiningen, which will be used from the command line. (install from here. You need java installed before)

1. can be started and configured extremely easy


Create a new project

lein new figwheel hello_world -- --reagent

And start it:

cd hello_world

lein figwheeel

Now, go in your browser to localhost:3449



It is already there!!!

2. instant feedback


Open the code using some tool, preferably LightTable and do this change in src/hello_world/core.cljs in the hello_world component:

{:style {:color "red"}}

Save the file.



It is already in the browser!!

now, let's start that in console.


(in-ns 'hello_world.core)

Now let's change the text to Hello Dan!

(swap! app-state assoc :text "Hello Dan!")


Boom!



 It is already in the browser. Figwheel takes care of all that!


Now 3 and 4 are answered by clojurescript which is based on the great programming language clojure. We're also using Reagent (clojurescript library on top of Facebook React) and figwheel which allows all the instant feedback stuff.



Saturday, October 17, 2015

REST api using Clojure and MySql. Is Clojure the most productive, robust language on the planet?

Updated 2019 code: https://gitlab.com/danbunea/production-ready-clojure

In my life, I have written a lot of web applications, using Java, then .NET, PHP and lately REST API;s using Python. I thought there cannot be anything to match Python productivity using Flask and SqlAlchemy until today.

Making a REST api in Clojure using Ring/Compojure and SqlKorma 

There is a great package manager called Leiningen (http://leiningen.org). To create a new web application, in a Terminal:

> lein new compojure todoapp2
> cd todoapp2

Update 2019

> lein new compojure-api asado +clojure-test
> cd asado

Now you have the skeleton application with a pretty known structure. Now we need to configure which packages will be used, editing package.clj:

(defproject todoapp2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [compojure "1.3.1"]
                 [ring/ring-core "1.3.2"]
                 [ring/ring-json "0.3.1"]
                 [ring/ring-defaults "0.1.4"]
                 [korma "0.3.0-RC5"]
                 [mysql/mysql-connector-java "5.1.6"]]
  :plugins [[lein-ring "0.8.13"]]
  :ring {:handler todoapp2.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring-mock "0.1.5"]]}})

Update 2019:


(defproject asado "0.1.0-SNAPSHOT"  :description "FIXME: write description"  :dependencies [[org.clojure/clojure "1.10.0"]
                 [metosin/compojure-api "2.0.0-alpha30"]
                 [ring/ring "1.6.3"]
                 [compojure "1.6.1"]
                 [manifold "0.1.8"]
                 [metosin/spec-tools "0.9.2"]

                 ;database                 [korma "0.4.3"]
                 [mysql/mysql-connector-java "8.0.12"]
                 ]
  :ring {:handler asado.handler/app}
  :uberjar-name "server.jar"  :profiles {:dev {:dependencies [[javax.servlet/javax.servlet-api "3.1.0"]
                                 [ring/ring-mock "0.3.2"]]
                  :plugins [[lein-ring "0.12.5"]]}})

Now to add all dependencies:

> lein deps 

Basically we need the json package, sqlkorma and the mysql JDBC driver. All installed. 

In MySql we'll create a database todo, where we create a table items, with id, title (varchar), is_complete (tinyint).

CREATE TABLE `items` (
  `id` int(5) NOT NULL AUTO_INCREMENT,
  `title` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `is_complete` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Now let's create a database.clj file where we configure the database connection details:

(ns todoapp2.database
  (:require [korma.db :as korma]))

(def db-connection-info (korma/mysql 
  {:classname "com.mysql.jdbc.Driver"
   :subprotocol "mysql"
   :user "root"
   :subname "//localhost:3306/todo"}))

; set up korma
(korma/defdb db db-connection-info)

Update 2019:


(ns asado.database
  (:require [korma.db :as korma]))

(def db-connection-info (korma/mysql                          {:classname "com.mysql.cj.jdbc.Driver"                           :subprotocol "mysql"                           :user "root"
                           :subname "//localhost:3307/todo"}))

; set up korma(korma/defdb db db-connection-info)

Now let's write the database access functions, in a new file: query.clj

(ns todoapp2.query
  (:require [todoapp2.database]
            [korma.core :refer :all]))

(defentity items)

(defn get-todos []
  (select items))

(defn add-todo [title]
  (insert items
          (values {:title title})))

(defn delete-todo [id]
  (delete items
          (where {:id [= id]})))

(defn update-todo [id title is-complete]
  (update items
          (set-fields {:title title
                       :is_complete is-complete})
          (where {:id [= id]})))

(defn get-todo [id]
  (first
    (select items
          (where {:id [= id]}))))

Update 2019:


(ns asado.query
  (:require [asado.database :refer [db]]
            [korma.core :refer :all]
            [schema.core :as s]))

(s/defschema TitleBody
             {:title s/Str})

(s/defschema TodoBody
             {:id s/Int
              :title s/Str
              :is_complete s/Bool})


(defentity items)

(defn get-todos []
  (select items))

(defn add-todo [title]
  (insert items
          (values {:title title})))

(defn delete-todo [id]
  (delete items
          (where {:id id})))

(defn update-todo [id title is-complete]
  (update items
          (set-fields {:title       title
                       :is_complete is-complete})
          (where {:id id})))

(defn get-todo [id]
  (first    (select items
            (where {:id id}))))

All done. SqlKorma is extremely easy to use, very composable.

Ok, now let's write the REST services:

(ns todoapp2.handler
  (:require [compojure.core :refer :all]
  [compojure.handler :as handler]
            [compojure.route :as route]
            [ring.middleware.json :as json]
            [ring.util.response :refer [response]]
            [todoapp2.query :refer :all]))

(defroutes app-routes
  (GET "/api/todos" []
       (response (get-todos)))
  (GET "/api/todos/:id" [id]
       (response (get-todo (Integer/parseInt id))))
  (POST "/api/todos" [title]
       (response (add-todo title)))
  (PUT "/api/todos/:id" [id title is_complete]
       (response (update-todo (Integer/parseInt id) title is_complete)))
  (DELETE "/api/todos/:id" [id]
        (response (delete-todo (Integer/parseInt id))))
  (route/resources "/")
  (route/not-found "Not Found"))

(def app
  (-> (handler/api app-routes)
      (json/wrap-json-params)
      (json/wrap-json-response)))

Update 2019, using compojure-api:

(ns asado.handler
  (:require [compojure.api.sweet :refer :all]
            [ring.util.http-response :refer :all]
            [schema.core :as s]
            [asado.query :as q]))


(def app
  (api    {:swagger     {:ui "/"      :spec "/swagger.json"      :data {:info {:title "Asado"                    :description "Compojure Api example"}
             :tags [{:name "api", :description "some apis"}]}}}

    (context "/api" []
      :tags ["api"]

      (GET "/todos" []
        (ok (q/get-todos )))
      (GET "/api/todos/:id" []
        :path-params [id :- s/Int]
        (ok (q/get-todo id)))
      (POST "/api/todos" []
        :body [title-body q/TitleBody]
        (let [{:keys [title]} title-body]
          (ok (q/add-todo title))))
      (PUT "/api/todos" []
        :body [todo-body q/TodoBody]
        (let [{:keys [id title is_complete]} todo-body]
          (ok {:updated (q/update-todo id title is_complete)})))
      (DELETE "/api/todos/:id" []
        :path-params [id :- s/Int]
        (ok {:deleted (q/delete-todo id)}))

      )))




Starting the server:

>lein ring start

Using a tool like Advanced REST Client plugin for Chrome will allow you to use the API:




And accessing http://localhost:3000/api/todos will show you what you created.

Update 2019



Conclusion


Pretty awesome!