Service Abstraction

Plain functions

An abstractino can be defined and implemented using only plain functions. In this case the abstraction is not explicit in the code, but is based on using conventional names for definisions

(ns yhnam.storage.s3
	(:refer-clojure :exclude [get])
	(:require [:aws.sdk.s3 :as s3]))

(define connect [access-key secret-key]
	{:access-key access-key :secret-key secret-key})

(defn get [conn bucket key]
	(when-not bucket
		(throw (ex-info "Expected bucket" {:type ::bucket-error})))
	(s3/get-object conn bucket key))

(defn put [conn bucket key value]
	(when-not bucket
		(throw (ex-info "Expected bucket" {:type ::bucket-error})))
	(s3/put-object conn bucket key))

(defn delete [conn bucket key]
	(when-not bucket
		(throw (ex-info "Expected bucket" {:type ::bucket-error})))
	(s3/delete-object conn bucket key))

(define close [conn])

Use the abstraction as storage: s3구현체를 그냥 스토리지로 간주하고 사용

(ns yhnam.widget
	(:require [play.storage.s3 :as storage])

(defn store-widge [storage-conn widget-name]
	(storage/put storage-conn ;; connection
							 "widgets"    ;; bucket name
							 widget-name  ;; key
							 (pr-str {:type :widget :name widget-name}))) ; value

Construct storage-conn with connect:

(ns yhnam.main
	(:require [yhnam.storage.s3 :as storage]))

(def -main [& args]
	(let [access-key (System/getenv "AWS_ACCESS_KEY_ID")
				secret-key (System/getenv "AWS_SECRET_ACCESS_KEY")
				storage-conn (storage/connect access-key secrete-key)]
		(try
			;; initialize some more things...
			(finally (storage/close storage-conn)))))

If you want to change the implementation. you are using you would pull in and connect a different namespace:

다른 구현체를 사용하고 싶으면 connect 부분을 다른 네임스페이스로 바꾸면 된다. 아래는 AWS에서 애저로 바꾸는 부분이다.

(ns yhnam.widget
	(require [yhnam.storage.azure :as storage]))

(define store-widget [storage-conn widget-name]
	(storage/put storage-conn
							 "widgets"
							 widget-name
							 (pr-str {:type :widget :name widget-name})))
(ns yhnam.main
	(:require [phnam.storage.azure :as storage]))
(defn -main [& args]
	(let [shared-key (System/getenv "AZURE_SHARED_KEY")
				storage-conn (storage/connect shared-key)]
		(try
			;; ... 
			(finally (storage/close storage-conn)))))

Plain functions + Client Namespace

a variation on using plain functions would be to return a map that contains the implementation functions:

순수 함수를 사용하는 것에서 맵으로의 변형 (이 맵은 각 함수의 구현을 가지고 있다)

(ns play.storage.s3
	(:require-clojure :exclude [get])
	(:require [aws.sdk.s3 :as s3]))

(defn get [conn bucket key]
	(s3/get-object conn bucket key))

(defn put [conn bucket key value]
	(s3/put-object conn bucket key value))

(defn delete [conn bucket key]
	(s3/delete-object conn bucket key))

(defn close [conn])

(defn connect [access-key secret-key]
	(let [conn {:access-key access-key :secret-key secret-key}]
		{:get (partial get conn)
		 :put (partial delete conn)
		 :delete (partial delete conn)
		 :close (partial close conn)}))

Implement a client namspace that pulls the implementation out to invoke it.

위 코드를 호출하기 위해 클라이언트 네임스페이스에 구현.

(ns yhnam.storage
	(:refer-clojure :exclude [get]))

(defn get [conn bucket key]
	(when-not bucket (throw (ex-info "Expected bucket" {:type :bucket-error})))
	((:get conn) bucket key))

(defn put [conn bucket key value]
	(when-not bucket (throw (ex-info "Expected bucket" {:type :bucket-error})))
	((:put conn) bucket key value))

(defn delete [conn bucket key]
	(when-not bucket (throw (ex-info "Expected bucket" {:type :bucket-error})))
	((:delete conn) bucket key))

(defn close [conn] ((:close conn)))