GithubHelp home page GithubHelp logo

brianium / yoose Goto Github PK

View Code? Open in Web Editor NEW
10.0 3.0 0.0 239 KB

:construction_worker: A Clojure library for use case driven development

License: MIT License

Clojure 100.00%
clojure async usecase

yoose's Introduction

yoose

Clojars Project

Finn Says Yussssss

Yoose attempts to encourage use case centered applications.

What is a use case?

In software and systems engineering, a use case is a list of actions or event steps typically defining the interactions between a role and a system to achieve a goal. The actor can be a human or other external system.

Yoose attempts to define a use case as a process created with input and output ports (currently via core.async channels). A use case should be able to gather input from it's input port to accomplish it's goal, and send output to it's output port to notify interested parties. Interested parties you say??!?!? An interested party might be an HTTP handler, a CLI application, a robotic arm? - the list goes on and on.

Usage

The following contrived example is inspired by the clean todos project.

(ns yoose.todos
  (:require [brianium.yoose.async :refer :all]))
  
(defusecase create-todo [this db]
  (let [entity (<in this)]
    (->> entity
         (save db)
         (create-message :todo/create)
         (>out this))))

The defusecase is an optional bit of syntactic sugar, but one I find useful. defusecase defines a function that is used for creating use cases. A use-case-factory if you will. These factories expect to be called with an input and output channel a la core.async, and any other dependencies needed to do the job. Or in clojure.spec parlance:

(s/def ::use-case-factory
  (s/fspec :args (s/cat
                   :in   ::in
                   :out  ::out
                   :deps (s/* any?))
           :ret  yoose.spec/use-case))

And so one might actually create an instance of a use case like so:

(def input (chan))

(def output (chan))

(def db (create-a-db))

(def use-case (create-todo input output db))

Notice how dependencies defined after this in the defusecase example are passed in after input and output.

Any number of additional arguments can be defined this way:

(defusecase create-todo [this db arg2 arg3] ...)

While this is possible, I would recommend a single dependency map. This is super convenient when passing around dependencies - say with a super cool library like mount - and destructuring is always your friend.

(defusecase create-todo [this {:keys [db arg2 arg3]}] ...)

**Note: use cases are currently built with core.async in mind, but it may not always be the case. It's possible that some day yoose.manifold or yoose.queue might burst onto the scene and define use case factories with different expectations. When in doubt - refer to the specs ;)

Documentation

brianium.yoose

brianium.yoose defines the api for exercising use cases. A use case is something that implements the brianium.yoose/UseCase protocol. Most of the functions in brianium.yoose just implement the functions defined by the protocol.

push!

Places a value into the use case input port

(push! use-case "some value!")

pull!

Calls the given function with the next value taken from the output port

(pull! use-case #(println %))

pull!!

Takes a value from the output port and returns it. Blocks until output is received

(def response (pull!! use-case))

<in

Takes a value from the input port. The use of this function is encouraged only in the context of defining a use case

(let [input (<in use-case)])

>out

Puts a value into the output port. The use of this function is encouraged only in the context of defining a use case

(>out use-case "an output message")

in

Returns the input port of the use case

(let [port (in use-case)])

out

Returns the output port of the use case

(let [port (out use-case)])

close!

Closes input and output ports

(close! use-case)

trade!!

Pushes a values into the use case and blocks until output is available.

(def result (trade!! use-case "hello"))

use-case?

Check if the given value implements the brianium.yoose/UseCase protocol

(use-case? value)

For more information - see the spec

brianium.yoose.async

Provides a core.async implementation of the brianium.yoose/UseCase protocol.

make-use-case

Creates a new use case backed by core.async

(require '[clojure.core.async :refer [chan]])

(def input (chan))

(def output (chan))

(def use-case (make-use-case input output))

<in

brianium.yoose/<in redefined as a macro. Since go macro translation stops at function creation boundaries - brianium.yoose/<in can't opt into a wrapping go block. Using brianium.yoose.async/<in circumvents this problem.

;;; BAD - throws error for using <! outside of go block
(go
  (let [input (brianium.yoose/<in use-case)]))
  
;;; OK
(go
  (let [input (brianium.yoose.async/<in use-case)]))

>out

brianium.yoose/>out redefined as a macro. See brianium.yoose/<in rationale above.

defusecase

Defines an async use case. A use case is really just a function that executes it's body in the context of a go loop.

(defusecase create-todo [this db]
  (let [entity (<in this)]
    (->> entity
         (save db)
         (create-message :todo/create)
         (>out this))))
		 
;; is equivalent to

(defn create-todo [in out db]
  (let [use-case (make-use-case in out)]
    (go-loop []
      (let [entity (<in this)]
        (->> entity
             (save db)
             (create-message :todo/create)
             (>out this)))
    (recur))
  use-case))

yoose's People

Contributors

brianium avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

yoose's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.