Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with a variable in a library that needs to be set outside of it?

I'm using Datomic in several projects and it's time to move all the common code to a small utilities library.

One challenge is to deal with a shared database uri, on which most operations depend, but must be set by the project using the library. I wonder if there is a well-established way to do this. Here are some alternatives I've thought about:

  • Dropping the uri symbol in the library and adding the uri as an argument to every function that accesses the database

  • Altering it via alter-var-root, or similar mechanism, in an init function

  • Keeping it in the library as a dynamic var *uri* and overriding the value in a hopefully small adapter layer like

    (def my-url ...bla...)

    (defn my-fun [args] (with-datomic-uri my-uri (apply library/my-fun args))

  • Keeping uri as an atom in the library

like image 698
konr Avatar asked Dec 19 '13 14:12

konr


2 Answers

There was a presentation from Stuart Sierra last Clojure/West, called Clojure in the Large, dealing with design patterns for larger Clojure applications.

One of those was the problem you describe.

To summarize tips regarding the problem at hand:

1 Clear constructor

So you have a well defined initial state.

  (defn make-connection [uri]
      {:uri uri
       ...}

2 Make dependencies clear

  (defn update-db [connection] 
     ...

3 It's easier to test

(deftest t-update
  (let [conn (make-connection)]
    (is (= ... (update-db conn)))))

4 Safer to reload

 (require ... :reload)

Keeping uri in a variable to be bound later is pretty common, but introduces hidden dependencies, also assumes body starts and ends on a single thread.

Watch the talk, many more tips on design.

like image 153
guilespi Avatar answered Sep 17 '22 22:09

guilespi


My feeling is to keep most datomic code as free of implicit state as possible.

Have query functions take a database value. Have write functions (transact) take a database connection. That maximizes potential reuse and avoids implicit assumptions like only ever talking to one database connection or inadvertently implicitly hardcoding query functions to only work on the current database value - as opposed to past (as-of) or "future" (with) database values.

Coordinating a single common connection for the standard use case of the library then becomes the job of small additional namespace. Using an atom makes sense here to hold the uri or connection. A few convenience macros, perhaps called with-connection, and with-current-db could then wrap the main library functions if manually coding for and passing connection and database values is a nuisance.

like image 38
Alex Stoddard Avatar answered Sep 17 '22 22:09

Alex Stoddard