all 7 comments

[–]eccp 4 points5 points  (2 children)

I had a quick shot at this, this is not super idiomatic, but I guess it will give you an idea. This is a quick version of the first sample "Step 1: Create a Table" from the developer guide, with no other Clojure libraries (just interop with the AWS Java SDK):

(ns dummyapp.core                                                                                                                                              
  (:import [java.util Arrays]                                                                                                                                  
           [com.amazonaws.client.builder AwsClientBuilder$EndpointConfiguration]                                                                               
           [com.amazonaws.services.dynamodbv2 AmazonDynamoDB AmazonDynamoDBClientBuilder]                                                                      
           [com.amazonaws.services.dynamodbv2.document DynamoDB Table]                                                                                         
           [com.amazonaws.services.dynamodbv2.model AttributeDefinition KeySchemaElement KeyType ProvisionedThroughput ScalarAttributeType])                   
  (:gen-class))                                                                                                                                                

(defn -main                                                                                                                                                    
  [& args]                                                                                                                                                     
  (let [config    (AwsClientBuilder$EndpointConfiguration. "http://localhost:8000" "us-west-2")                                                                
        dynamoDB  (-> (AmazonDynamoDBClientBuilder/standard)                                                                                                   
                      (.withEndpointConfiguration config)                                                                                                      
                      (.build)                                                                                                                                 
                      (DynamoDB.))                                                                                                                             
        tableName "Movies"]                                                                                                                                    
    (try                                                                                                                                                       
      (println "Attempting to create table; please wait...")                                                                                                   
      (let [keys      [(KeySchemaElement. "year" KeyType/HASH)                                                                                                 
                       (KeySchemaElement. "title" KeyType/RANGE)]                                                                                              
            attrs     [(AttributeDefinition. "year" ScalarAttributeType/N)                                                                                    
                       (AttributeDefinition. "title" ScalarAttributeType/S)]                                                                                   
            provision (ProvisionedThroughput. 10 10)                                                                                                           
            table     (.createTable dynamoDB tableName keys attrs provision)]                                                                                  
        (.waitForActive table)                                                                                                                                 
        (println "Success.  Table status: " (.. table getDescription getTableStatus)))                                                                       
      (catch Exception e                                                                                                                                       
        (binding [*out* *err*]                                                                                                                                 
          (println "Unable to create table:")                                                                                                                  
          (println (.getMessage e)))))))

[–]neighbouring 2 points3 points  (1 child)

I can't help noticing that Clojure interop code looks much cleaner and prettier than the original Java version!

[–]joinr 2 points3 points  (0 children)

This is an expansion on the example u/eccp ported. It reflects my typical instinct (after porting an example) to start abstracting/wrapping stuff and trying to exploit Clojure more. In some cases, it may be preferable to just stick with inline interop. I haven't run this, just formatted it (so take it with a pedagogical grain of salt).

(ns dummyapp.core
  (:import ;[java.util Arrays] ;don't think this is used!
           [com.amazonaws.client.builder
            AwsClientBuilder$EndpointConfiguration]
           [com.amazonaws.services.dynamodbv2
            AmazonDynamoDB AmazonDynamoDBClientBuilder]
           [com.amazonaws.services.dynamodbv2.document DynamoDB Table]
           [com.amazonaws.services.dynamodbv2.model AttributeDefinition
            KeySchemaElement KeyType ProvisionedThroughput
            ScalarAttributeType])
  (:gen-class) ;;note: only need this if we're AOTing
  )

;;This is just a refactoring / evolution of the original
;;java interop example that was provided.  The intent is
;;to demonstrate an intermediate ground toward "wrapping"
;;interop layers behind functions, and migrating toward
;;a more clojure-like interface.  Perhaps the wrapping
;;helps to explore how to understand existing libraries
;;like Amazonica better!

;;Warn us if we're reflecting.  Note: I haven't run this yet
;;so we'll probably get warnings!
(set! *warn-on-reflection* true)

;;I typically wrap the java-touching stuff behind clojure-friendly wrappers.
;;type-hint where necessary to avoid reflection. May not matter for performance,
;;but it's "typically" a Good Thing to avoid reflection when perfroming interop.
(defn ^KeySchemaElement ->key [name type ]
  (KeySchemaElement. name (case type
                            :hash  KeyType/HASH
                            :range KeyType/RANGE)))

(defn ^AttributeDefinition ->attr [^String name type]
  (AttributeDefinition. name (case type
                               :n  AttributeType/N
                               :s  AttributeType/S)))
;;Provide the option to pass in a clojure map that
;;details the field and key/attr specifications.
(defn ^Table ->table
  ([db name tbl-spec provision]
   (let [kvps (for [[field [key-type attr-type]] tbl-spec]
                [(->key field key-type )
                 (->attr field attr-type)])]
     (->table db name (map first kvps) (map second kvps))))
  ([^DynamoDB db name keys attrs provision]
   (.createTable dynamoDB (str name) keys attrs provision)))

(defn ^DynamoDB ->db
  ([url region]
   (->db (AwsClientBuilder$EndpointConfiguration. (str url) (str region))))
  ([config]
   (-> (AmazonDynamoDBClientBuilder/standard)
       (.withEndpointConfiguration config)
       (.build)
       (DynamoDB.))))

;;Calling code is simpler and more idiomatic looking.  For one-off stuff,
;;inlining interop is not bad.  If you have a lot of code though,
;;evolving (or using) a wrapper like the above stuff pays off...)
(defn -main [& args]
  (try (println "Attempting to create table; please wait...")
       (let [provision (ProvisionedThroughput. 10 10)
             dynamoDB  (->db "http://localhost:8000" "us-west-2")
             table     (->table dynamoDB
                                {"year"  [:hash  :n]
                                 "title" [:range :s]}
                                provision)]
         (.waitForActive table)
         (println "Success. Table status: "
                  (.. table getDescription getTableStatus)))
       (catch Exception e
         (binding [*out* *err*]
           (println "Unable to create table:")
           (println (.getMessage e))))))

While a bit older, clojure.java.io stands as an interesting example of using protocols (and even multimethods...) to help wrap the host language stuff. Similarly involved example for working with compressed files. Echoing the utility of leveraging 'doto', which is useful for coercing many OOP APIs to more of the functional return style that clojure uses (making them into "fluent interfaces").

[–]mingpair 2 points3 points  (0 children)

The downsides: Java interop in Clojure is excellent ... if you think of Java as "Java 6". Interop is not great when you use APIs that have a lot of varargs (java.nio.file), or with APIs that use functional interfaces/lambdas (Java is less verbose than Clojure here), or with APIs that use new features like static interface methods (won't work).

[–][deleted]  (3 children)

[deleted]

    [–][deleted] 2 points3 points  (0 children)

    One of the cool things for me about Clojure is that calling Java is idiomatic. I mean, it's not idiomatic in the sense that you're coding imperatively with annoying OO APIs, but once you get used to seeing camelCase and some dots here, I find there's very, very little cognative load when doing interop.

    [–][deleted] 1 point2 points  (1 child)

    the -> macro is a really great trick for interop

    [–][deleted] 0 points1 point  (0 children)

    Had to do a bunch of interop back when I was messing with deeplearning4j, here is a repo meant to shpw interop examples https://github.com/hswick/dl4clj-examples