> On 2016-08-27, at 09:09, Georgi Danov <[email protected]> wrote:
>
> I have om.next app and am building two different sets of mutate/read fns
> because I want to have switchable persistence.
>
> I am pondering over the design of the coupling between the UI and the data
> layer and in particular — how to ensure that I spot as early and reliable as
> possible inconsistencies between the persistence implementations and the UI.
If I understand you correctly, you should definitely keep your persistence
implementation and the UI separate. Indeed, the client shouldn't care at all
what the persistence is on the remote (server) side.
Here's how I've structured my application:
* Om Next client (using Untangled client) calls the remote API with read and
mutate calls.
* Web server (e.g., Pedestal, Ring, HTTP Kit) which includes the Om parser
(ns myapp.server.api.parser)
* The Om parser read and mutate implementations are pretty simple.
They primarily call the core business application code which is in its
own namespace (myapp.server.application) and knows nothing about the
web server. Likewise there's no business logic in the parser. It pretty
much takes data from the request and session, passes them to the application
functions, and wraps responses from the application in the
appropriate {:action (fn []...)} and {:value {:tempids {}}} maps.
* The application code knows nothing about the persistence layer implementation.
I have protocols set up that define the interface between the two,
and different persistence types implement the protocol functions. In my
case I have Postgres (production) and H2 (test) implementations. Granted,
they're both JDBC, but they're different enough that I've found it useful
to use both. The functions that need different implementations for H2 and
Postgres I have implemented as multi methods that switch on the database
server type.
So, on the server I have
(ns myapp.server.component.api.parser
(:requires [com.stuartsierra.component :as component]
[myapp.server.component.application :as app]
[om.next.server :as om))
(ns myapp.server.component.application
"Business logic of the application"
(:requires [com.stuartsierra.component :as component]
[myapp.server.component.application.protocols :as p]))
(ns myapp.server.database.sql
"Persistence layer."
(:requires [com.stuartsierra.component :as component]
[clojure.java.jdbc :as jdbc]
[myapp.server.component.application.protocols :as p]
[myapp.server.component.database.impl.sql :as sql))
Notice in this set up, the persistence layer knows nothing about the parser.
Indeed it knows nothing about even the application other than the protocols
its implementing. For simple reads and mutates, the functions at each layer
look very similar, and there's a lot of stuff that's passed through, but I've
found it helps me think about the application by keeping everything decoupled.
Because spec.fdef doesn't work with protocol function implementations, I have
the implementation functions in turn call plain functions. I think this might
actually be inverted from the way it should be, because I should have the spec
in the application code rather than the implementation code to reduce code
duplication, but that's refactoring for another day.
> Example: changing the data structure by adding a field, putting this in the
> UI but forgetting to adapt one of the read functions. I've considered using
> clojure.spec in a thin repository layer that only validates the data coming
> from the persistence implementation, however the general message I've heard
> is that clojure.spec is not supposed to be used in production mode.
I've just started using clojure.spec, and haven't done any profiling to see
what the impact is. So far I've found fdef useful in catching malformed
arguments. I include the spec definitions in the source, but only instrument
them in testing.
> What's your advice on structuring the mutation functions? Is it advisable to
> use om/transact! directly in the UI code, or do you have some API (like
> Repository pattern) that hides the details?
On the client side I have a mix of using om/transact in the UI code in the UI
and wrapping the om/transact calls in functions. I'm also trying to figure out
how to make reusable components. My ui.cljs file is getting longer than I'd
like, so I'm going to be looking at how to split that up in logical ways.
I'm not familiar with the Repository Pattern by name, but I definitely want to
hide implementation details (both on the web server and database sides) from
application logic.
I'm wondering if I've completely misunderstood your questions. In particular,
when I think persistence I think server side. Perhaps you're thinking of
storage on the client? Given that this is on the ClojureScript list maybe I'm
off-base discussing so much of the server-side.
Hope this helps.
Michael Glaesemann
grzm seespotcode net
--
Note that posts from new members are moderated - please be patient with your
first post.
---
You received this message because you are subscribed to the Google Groups
"ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/clojurescript.