Giter Club home page Giter Club logo

tenet's Introduction

license https://github.com/lazy-cat-io/tenet/releases clojars

codecov build deploy

tenet

A Clojure(Script) library, which helps to create explicit and understandable results to unify and simplify the data flow.

Rationale

Problem statement

Usually, when working in a team, it is necessary to agree beforehand on the type of results to be used. Someone uses maps, someone uses vectors, someone uses monads like Either, Maybe, etc.

It is not always clear when a function returns data without some context (e.g. nil, 42, etc).

What does nil mean: No data? Didn’t do anything? Did something go wrong?

What does 42 mean: User id? Age?

Such answers make you look at the current implementation and spend time understanding the current context.

Imagine that we have a function containing some kind of business logic:

(defn create-user! [user]
  (if-not (valid? user)
    ;; returns the response that the data is not valid
    (if-not (exists? user)
      ;; returns the response that the email is occupied
      (db/insert! user)))) ;; returns the response that the user was created or an error occurred while writing data to the database

In this case, there may be several possible responses:

  • the user data is not valid

  • the email is occupied

  • an error occurred while writing data to the database

  • or finally, a response about a successful operation: e.g. user id or data

There is a useful data type in the Clojure - a keyword that can be used to add some context to the response:

  • :user/incorrect, :user/exists

  • :user/created or :org.acme.user/created

Having such an answer, it is immediately clear what exactly happened - we have the context and data. Most of the time we do not write code, we read it. And this is very important.

We have decided on the context, but how to add it? Key-value in the map? Vector? Monad? Metadata? And how to understand which answer is considered an error (anomaly)?

We used all the above methods in our practice, and it has always been something inconvenient.

What should be the structure of the map, vector? Create custom object/type and use getters and setters? This adds problems in further use and looks like OOP. Use metadata? Metadata cannot be added to some types of data. And what type of response is considered an error (anomaly)?

Solution

This library helps to unify responses and anomalies. Out of the box, there are general types of responses and all the necessary helper functions.

In short, all the responses are a Pair [type data]. E.g. [:org.acme.user/created {:user/id 42}].

There are no requirements for the type of response and the type of data. Always the same data structure.

We have a registry of basic anomalies to which you can add your own type, or you can use the global hierarchy using derive from the parent :tenet.response/error. The registry was added to increase the performance. Checking an anomaly in the registry takes ~15-25 ns, and checking using the global hierarchy takes ~120-150 ns.

See the performance tests.

Getting started

Add the following dependency in your project:

project.clj or build.boot
[io.lazy-cat/tenet "1.0.67"]
deps.edn or bb.edn
io.lazy-cat/tenet {:mvn/version "1.0.67"}

Limitations

This library doesn’t work with babashka (sci) - currently deftype is not supported.

Basic API

(ns example
  (:require
    [tenet.response :as r]))

(r/anomaly? x)
;; Exception and js/Error are anomalies
;; Object, nil, default are not anomalies
;; Other data types are anomalies if they are registered in the registry or inherited from `:tenet.response/error`

(r/as-response x :your-response-type)

Anomaly registry

:busy
:conflict
:error
:forbidden
:incorrect
:interrupted
:not-found
:unauthorized
:unavailable
:unsupported

Response builders

Error response builders
(r/as-busy x)
(r/as-conflict x)
(r/as-error x)
(r/as-forbidden x)
(r/as-incorrect x)
(r/as-interrupted x)
(r/as-not-found x)
(r/as-unauthorized x)
(r/as-unavailable x)
(r/as-unsupported x)
Success response builders
(r/as-accepted x)
(r/as-created x)
(r/as-deleted x)
(r/as-found x)
(r/as-success x)
(r/as-updated x)
Custom response builders
(derive :org.acme.user/incorrect ::r/error) ;; or (swap! r/*registry conj :org.acme.user/incorrect)

(r/as :org.acme.user/incorrect :foo) ;; => #tenet [:org.acme.user/incorrect :foo]
(-> (r/as :org.acme.user/incorrect) (r/anomaly?)) ;; => true
(-> (r/as :org.acme.user/incorrect :foo) (r/anomaly?)) ;; => true

(r/as :org.acme.user/created :foo) ;; => #tenet [:org.acme.user/created :foo]
(-> (r/as :org.acme.user/created :foo) (r/anomaly?)) ;; => false

Examples

Basic API
(r/as-not-found 42) ;; => #tenet [:not-found 42]

(r/anomaly? (r/as-not-found 42)) ;; => true

(r/anomaly? (r/as-created 42)) ;; => false

(:type (r/as-created 42)) ;; => :created

(:data (r/as-created 42)) ;; => 42

@(r/as-created 42) ;; => 42

(-> (r/as-created 42)
    (with-meta {:foo :bar})
    (meta)) ;; => {:foo :bar}
Destructuring
(let [[type data] (r/as-not-found 42)]
  {:type type, :data data}) ;; => {:type :not-found, :data 42}

(let [{:keys [type data]} (r/as-not-found 42)]
    {:type type, :data data}) ;; => {:type :not-found, :data 42}
Update response type
(-> (r/as-not-found 42)
    (r/as-incorrect)) ;; => #tenet [:incorrect 42]
Update response data
(-> (r/as-not-found 42)
    (r/as-incorrect)
    (update :data inc)) ;; => #tenet [:incorrect 43]

(-> (r/as-not-found {:foo {:bar 42}})
    (r/as-incorrect)
    (update-in [:data :foo :bar] inc)) ;; => #tenet [:incorrect {:foo {:bar 43}}]

Helper macros

(def boom!
  (constantly :error))

;; just like `some->`, but checks for anomalies
(r/-> 42 inc) ;; => 43
(r/-> 42 inc boom!) ;; => :error
(r/-> 42 inc boom! inc) ;; => :error


;; just like `some->>`, but checks for anomalies
(r/->> 42 inc) ;; => 43
(r/->> 42 inc boom!) ;; => :error
(r/->> 42 inc boom! inc) ;; => :error


;; handle exceptions
(r/safe (Exception. "boom!")) ;; => nil
(r/safe (Exception. "boom!") #(r/as-error (ex-message %))) ;; => #tenet [:error "boom!"]

tenet's People

Contributors

just-sultanov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

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.