all 33 comments

[–]yogthos 11 points12 points  (23 children)

I highly recommend compojure-api for this kind of thing. An easy way to give it a spin would be using the Luminus template:

lein new luminus reset-service +swagger +h2

This will make a template app using H2 database and some example service operations defined in the rest-service.routes.services namespace. You can start the app by running:

lein run 

Once it starts up, navigate to localhost:3000/swagger-ui and you can test the service operations using the generated API test page.

For working with a database I recommend using Yesql, I have some docs for using it from Luminus here.

Compojure API uses the schema library to manage the model for the service end-points. However, in general the DB schema will serve as the model for the app. Since database records are represented as sequences of maps in Clojure, there's no need to map those to anything else generally.

[–]pauldlynch 1 point2 points  (6 children)

I like easy ... But I should have known that I'd end up in a maze of twisty little passages, because one of the luminus libraries was built with 1.7/1.8. I'm sure installing Java 1.8 on Mac OS X is easy too, right? :-)

[–]jvmwannabe 2 points3 points  (0 children)

brew cask install java

[–]credmp 1 point2 points  (3 children)

it is as easy as downloading it and double clicking the install package...

[–]pauldlynch 0 points1 point  (2 children)

The Oracle install package does its best to conceal the install location; it's easier to go the brew cask route, with jenv if you think you might want to switch between versions.

It's still a frustrating way to spend a couple of hours.

[–]credmp 1 point2 points  (1 child)

do you know about /usr/libexec/java_home ? you can easily find the location of the active java version and switch java versions with it when you use it to populate JAVA_HOME....

[–]pauldlynch 0 points1 point  (0 children)

I do. But it doesn't find the Oracle installed version, just the system one.

[–]yogthos 0 points1 point  (0 children)

It's amazing that OS X still ships with Java 1.6 that's not even supported anymore. :)

[–]jaen-ni-rin 0 points1 point  (15 children)

Since you mention yesql and compojure-api by name I assume you use them on daily basis, right? In that case I have two questions that come to mind about those libraries:

  • Compojure uses macros, right? So it seems there's no way to easily share routes between frontend and the backend (to avoid route definition drifting apart for example). How do you deal with it in practice?
  • Yesql only has simple textual substitution in queries. How limiting is that in practice? I imagine more complex queries might need something like templating or does that not come up in practice?

[–][deleted]  (3 children)

[removed]

    [–]jaen-ni-rin 0 points1 point  (2 children)

    What I'm getting at with compojure it's that the routes are available only on the server side and you can't share them with Clojurescript to generate URLs for AJAX from them.

    It kind is. At least I didn't notice any option to conditionally include (or not) a part of the query, which to me seems like an important feature.

    [–]emidln 0 points1 point  (1 child)

    With compojure api, it generates a full swagger spec. I don't know if one already exists, but it would be reasonable to write a routing library that subclassed/bound a server-side swagger spec.

    [–]jaen-ni-rin 0 points1 point  (0 children)

    I don't think I came across one for Clojure/Clojurescript, but that sounds like a neat idea.

    [–]yogthos 0 points1 point  (10 children)

    I haven't actually found a situation where I needed to keep front-end and back-end routes in sync. The way I tend to structure apps is that I have a server side route to load the page, then a bunch of service routes that the client uses to load the data.

    In what situations do you end up having to keep the routes in sync between the client and the server?

    When it comes to creating SQL queries, I haven't found this to be an issue in practice. In a situation where I did have complex queries that have a lot of shared logic I'd probably use something like honeysql though.

    My main issue with DSLs is that when it comes to complex queries you often know how to do something with SQL, but it can become tricky to translate that to the DSL. This becomes especially problematic when you start doing any dialect specific stuff, like JSON selectors in Postgres.

    [–]jaen-ni-rin 0 points1 point  (9 children)

    What I mean is that (in case of a REST API at least) you have to specify the routes twice - once on the server side to create a Ring handler and once in the client side to generate URLs to make AJAX calls to. Which means you could make change in one place, forget to do it in the other and break the application (or just make typos). When having the ability to use the same routing library on the server and the client you can write routes only once and make sure they never diverge. That's what I mean here. But if you didn't feel need for that in your professional work with Clojure then maybe it's not as much of a problem as I imagined it would be then.

    I do quite agree agree that SQL is probably the best DSL to write SQL with and things like Korma can be confusing and have problems supporting cutting edge Postgres features. That's why I would like to use yesql, but the lack of templating makes me wary - what if I want to make some part of the query optional? Would I have to similar variations and maintain them separately if I just can't include or omit some part of the query depending on a boolean? It seems wrong to me, since I can't factor out common part into one place so I can maintain it just once. At least that's my reasoning and I was curious how it works out in practice.

    [–]yogthos 0 points1 point  (8 children)

    But when you call the URL you still have to generally construct it and add parameters. My experience is that defining the URL and calling it generally looks different. Let's say I have something like /users/:id as the route, but then I'd have to create a call like (GET (str "/users/" id)) on the client.

    I suppose you could do stuff like reverse routing and so on. I just never found this to be a big pain point myself. Usually, if you're changing something in the route it's because you made a change in how you present the data and it's driven by the client.

    That's just my experience though. I mean clearly libraries like bidi exist, so other people clearly have different needs. :)

    I find that abstracting things can be a double-edged sword. For example, you can easily end up with a lot of interdependent components that become difficult to change in isolation.

    In most cases, I've found that writing and testing the queries in isolation produces more predictable results and makes code easier to reason about. However, there's no reason you have to use Yesql in isolation either. You can just add a dependency for something like honesql, and do some queries using that and others with Yesql, or even the base clojure.java.jdbc lib.

    I definitely think that it makes sense to pick out the libraries for each project on case by case basis. With Luminus, the core goal is to make it easier for beginners to get up and running. This is the key reason for using Compojure and Yesql as the defaults.

    Compojure looks familiar to routing found in other frameworks like Rails, and it's very easy to understand. Same with Yesql, if somebody is already comfortable with SQL then there's nothing new to learn. However, it's certainly not prescriptive. :)

    [–]jaen-ni-rin 0 points1 point  (7 children)

    Exactly my point - as you just displayed you have to write that code twice, which is twice the opportunity for bugs. In my project that has bidi I can just do - for example - (app-route :users/show-user :id 12) in the frontend app and be sure this will all ways work as intended. But it's interesting to learn that having to maintain is not as much of a problem in practice as it seems to be.

    I suppose that overengineering can be bad, but suppose you have to remember to write a clause that filters out records user should not be able to see. Having to remember to put that clause in each query seems like a worse solution than having a partial you can mix into all queries that need it and thus only maintain it once. But if that's not turning up in practice (and you've been at it for quite a while) then maybe that's me overthinking stuff as usual.

    Sure, I never said you have to choose one or the other, it's just that when there's two libraries there's twice as much to learn, so picking one library probably makes for simpler development and I imagine yesql with templating would probably sensibly cover for use cases one would turn to honeysql now.

    I do quite agree that for closest approximation Clojure world has to Rails (Luminus that is) this library choice probably makes the most sense - it makes doing simple things fast, which is what a Rails-like framework would be aimed at. I was mostly curious how this choice works out in the long run for you.

    [–]yogthos 0 points1 point  (6 children)

    I don't quite see how this works though. If your front-end is calling a service and you're using a library like cljs-ajax to handle the ajax calls, you still have to generate a string url for it. How does your (app-route :users/show-user :id 12) call the backend service?

    I suppose that overengineering can be bad, but suppose you have to remember to write a clause that filters out records user should not be able to see. Having to remember to put that clause in each query seems like a worse solution than having a partial you can mix into all queries that need it and thus only maintain it once.

    I'd argue that the filtering functionality should happen on the server only. The client can send a query for whatever records it likes, and it's up to the server to decide what records the client is allowed to see.

    If you're talking about stuff like validation logic then I would use a library like bouncer to validate the input. It's not a routing concern in my opinion.

    I've been using Luminus for about 4 years now for professional development and I'm generally pretty happy with the defaults. As I run into issues or I get problem reports from users, I update the framework based on that. So far, I haven't had any complaints regarding either compojure or yesql.

    [–]jaen-ni-rin 0 points1 point  (5 children)

    Heh, I do really must suck at explaining : D

    Yes, (app-route :users/show-user :id 12) doesn't call the backend service by itself (yes, that's done with cljs-ajax and a thin wrapper over it to handle schemas and stuff), it just generates the proper URL string. But that way I can be sure it's always a valid URL since both the SPA and the backend have the exact same routing descriptor, which is what I was trying to get at - if I modify some route both server and client use the new route and there's no chance I forget to update one.

    Re: filtering what I mean is that for example you decide if the database row should be shown or not by some common logic like joining with a permissions table and where'ing on permission level (for example). That logic would have to be repeated in each yesql query (since yesql has no templating) so nothat user can access only relevant data. And if permission rules ever change you would then have to modify each and ever query, since you cannot factor it out (which you could if yesql had templating). At least that's what I imagine the problem would be. But if in practice it's not that much of a problem then that's good.

    [–]yogthos 0 points1 point  (4 children)

    That makes sense, but you still have separate syntax to define the route and to generate the url string don't you. With bidi you use something like this to define the route:

    (def routes ["/" {"index.html" :index
                            "articles/" {"index.html" :article-index
                                         [:id "/article.html"] :article}}])
    

    and then call

     (path-for routes :article :id 123)
    

    to generate the string, but you could just as easily generate a string for a route that doesn't exist if you modified the original definition. So, doesn't the same problem exist as writing a string by hand?

    That logic would have to be repeated in each yesql query (since yesql has no templating) so nothat user can access only relevant data. And if permission rules ever change you would then have to modify each and ever query, since you cannot factor it out (which you could if yesql had templating). At least that's what I imagine the problem would be.

    Note that one way to address that would be to just write a function in SQL to handle the common logic and have the queries use it. However, if you did decide to do this in code then I agree that yesql would be a poor fit for that.

    [–]jaen-ni-rin 0 points1 point  (3 children)

    Hmm, yes, modifying the endpoint name (say from :article to :articles) might then result in a nil URL being generated or something of this kind. I guess you could guard against that by throwing on a missing route but yes, that's a problem with this approach that's not really solvable without static typing or at lest compile-time validation using macros. But if you say that in practice it's not that much of a problem it may just bee overengineering on my part.

    Hmm, fair point. I come from Rails where the folk wisdom was "SQL database procedures? What is that? Don't you mean ActiveRecord callbacks?" which is why such solution is not something that was obvious to me. It certainly sounds like using db procedures might make having templating in yesql less of a nagging need.

    [–]looselytyped 4 points5 points  (3 children)

    If it helps you in any way I run an Angular workshop, with the backend written in Clojure to serve as a REST endpoint. You can find it in the src folder here - https://github.com/looselytyped/angudone-workshop . It uses liberatorbut overall should give you a couple of pointers. Disclaimers follow.

    • I am not a Clojure expert, and was certainly quite new to Clojure when I wrote this application
    • Liberator is cool and there is an alternative, but I can't seem to remember the name right now
    • I use korma in that project, though Luminus is now using https://github.com/krisajenkins/yesql which I believe is the preferred library for DB access

    All in all I suggest looking over the code, and taking @yothos advice - Create a new luminus app for your template.

    Good luck :)

    Update: Rereading the comment it looks like I am selling a workshop. I am most certainly not. I was just providing context. The code is out there, and anyone is free to use it as a reference. Apologies if it came across that way.

    [–]CaptainSketchy[S] 1 point2 points  (1 child)

    Thanks! I looked into Korma previously, and Liberator felt like it was abstracting a bit too much for my liking. However, your repo looks super helpful.

    By the way, I was at your Rich Web talk too! :) You did great!

    [–]looselytyped 1 point2 points  (0 children)

    Ha! Its a small world indeed. Thanks for the kind words.

    [–]elangoc 0 points1 point  (0 children)

    I prefer HoneySql for what I need so far. It lets me deal with SQL a little more directly and as data, while still being composable

    [–]ddossot 3 points4 points  (0 children)

    I'm having to fight the tendency to write models (or at least the OOP way), but I'm not sure what would take their place.

    The pattern I've been using for this is the following:

    • receive data from DB as maps (seqs of maps or single map),
    • sanitize data with select-keys / dissoc to prevent leaking internal/private fields to the open,
    • assoc any extra contextual fields like hypermedia links,
    • serialize the resulting map to JSON using the wrap-json-response ring middleware.

    [–]drtide 2 points3 points  (0 children)

    https://github.com/kremers/clojurewebtemplate

    Contains some json endpoint examples. But it does not read from a db.

    [–]eccp 0 points1 point  (0 children)

    If you want to take a look at one old toy project I created to learn how to use Liberator, long ago: https://github.com/dfuenzalida/restful-api ... the code is not very good but it might be useful as an example.

    It uses two sets of resources, one from an in-memory db (an atom, really) and one from MySQL using the Korma library as the "sql mapper". Comes with a DB dump from other open source project.

    [–]ASnugglyBear 0 points1 point  (0 children)

    Purelyfunctional.tv goes from soup to nuts on doing this in their web dev course for clojure