Saturday, May 31, 2014

Clojure protocols example

Task: create a data provider with different implementations e.g. database / memory

;We start with a basic in-memory implementation:
(defprotocol DataProvider
  ;We pass the instance to the functions as "this" (the name is arbitrary)
  (find-user [this id]))

(deftype MemoryDataProvider []
  DataProvider

  (find-user [this id]
    (first (filter #(= (:id %) id)
                   [
                    {:id 1 :name "betty"}
                    {:id 2 :name "mike"}]))))

;Test: create an instance and find user with id 2
(def my-data-provider (MemoryDataProvider.))
(find-user my-data-provider 2)

;In the database implementation we need to pass e.g. host and port
;in order to establish a connection. We do this using the constructor
;function of the type (see DBDataProvider below). 
;We also need to add a function to establish the connection. We have to
;do add it also to the protocol, since the client has to call it and doesn't know 
;the implementation. We add a generic method "init" to the protocol, 
;for this purpose.
;(Note that "init" means side effects, so this doesn't really make
;sense in the in-memory implementation, but don't see a better solution).
(defprotocol DataProvider
  (init [this])
  (find-user [this id]))

;We have to add the method to MemoryDataProvider. Not doing this will
;not cause compiler error, but will throw runtime error if init is
;called on this type
(deftype MemoryDataProvider []
  DataProvider

  (init [this])
  
  (find-user [this id]
    (first (filter #(= (:id %) id)
                   [
                    {:id 1 :name "betty"}
                    {:id 2 :name "mike"}]))))


;Test
(def my-data-provider (MemoryDataProvider.))
(init my-data-provider) ;nil
(find-user my-data-provider 2)


;This is the database data provider type
(deftype DBDataProvider [host port]
  DataProvider

  (init [this]
    (println "host: " host ", port: " port)
    ;connect...
    )
  
  (find-user [this id]
    ;get it from database...
    {:id 0 :name "test"}
    )
  )

(def my-data-provider (DBDataProvider. "127.0.0.1" "1234"))
(init my-data-provider) ;nil
(find-user my-data-provider 2)


;We can also make init return the type:
(deftype DBDataProvider [host port]
  DataProvider

  (init [this]
    (println "host: " host ", port: " port)
    ;connect...
    this
    )
  
  (find-user [this id]
    ;get it from database...
    {:id 0 :name "test"}
    )
  )

;then we can do:
(def my-data-provider (init (DBDataProvider. "127.0.0.1" "1234")))
(find-user my-data-provider 2)


Note: I'm new to Clojure, glad to receive corrections or suggestions in the case something can be improved.
These snippets can be found (together with others) here.

No comments:

Post a Comment