Giter Club home page Giter Club logo

framework's Introduction

README

Introduction

Xiana is a lightweight web-application framework written in Clojure LISP, its goal is to be simple, blasting fast, and most important - a welcome platform for web programmers from different backgrounds that want to experience the wonders of functional programming!

It’s easy to install, fun to experiment and a powerful tool to produce reliable code for the world wild web.

Dependencies

System

Mandatory

  • Clojure 1.10
  • Postgresql >= 11.5

Optional

  • Docker >= 19.03.11
  • Docker-compose >= 1.21.0

Clojure

Tools

NameVersionDescription
leiningen2.9.0Project Manager

Libraries

Mandatory

NameVersionRelated
funcool/cats2.4.1Monad
funcool/cuerdasRELEASEMonad
metosin/reitit0.5.12Routes
potemkin/potemkin0.4.5Helper
com.draines/postal2.0.4Email
duct/server.http.jetty0.2.1WebServer
seancorfield/next.jdbc1.1.613WebServer
honeysql/honeysql1.0.444PostGreSQL
nilenso/honeysql-postgres0.2.6PostGreSQL
org.postgresql/postgresql42.2.2PostGreSQL
crypto-password/crypto-password0.2.1Security

Optional

NameVersionProvide
clj-kondo/clj-kondoRELEASETests

Development

Setup

QuickStart

$ git clone [email protected]:Flexiana/framework.git; cd framework
$ ./script/auto.sh -y all

The first command will clone the Flexiana/framework repository and jump to its directory. The second command calls auto.sh script to perform the following sequence of steps:

  1. Download the necessary docker images
  2. Instantiate the database container
  3. Import the initial SQL schema: ./docker/sql-scripts/init.sql
  4. Populate the new schema with ‘fake’ data from: ./docker/sql-scripts/test.sql
  5. Call lein test that will download the necessary Clojure dependencies and executes unitary tests.

See ./script/auto.sh help for more advanced options.

Remember it’s necessary to have docker/docker-compose installed in your host machine. Docker daemon should be initialized a priori, otherwise the chain of commands fails.

It should also be noted that after the first installation everything will be cached preventing unnecessary rework, it’s possible to run only the tests, if your development environment is already up, increasing the overall productivity.

./script/auto.sh -y tests

CLI

We define some aliases to make possible to use deps.edn directly (recommend).

Docker/Compose

Leinigen

Using lein directly is very simple:

lein test

The available commands (aliases):

AliasesCommandDescription
runlein run
testlein testInvoke tests

Hello World

Mandatory hello-word example:

(ns framework.app.hello-word
  (:require
   ;; mandatory modules to build/run any web application
   [xiana.core :as xiana]
   [framework.route.core :as route]
   [framework.webserver.core :as webserver]))

;; application route definitions
(def app-routes
  [["/" {:action
         #(xiana/ok
           (-> % (assoc :response
                        {:status 200 :body "Hello Word!"})))}]])

(defn -main
  "Application entry point."
  []
  ;; setup app routes
  (route/reset app-routes)
  ;; start app webserver
  (webserver/start []))

The first line defines the application and the following :require expression imports the necessary modules to build/run it.

Common web applications will use the following components:

  • Xiana (Monads)
  • State (Context)
  • Routes (URI)
  • Interceptors (Helpers)
  • WebServer (Http Request)
  • Postgresql (Database)

More details in the sections below.

Flow

./resources/images/flow.png

This is our data flow, some details were omitted by the sake of simplicity, but is a good representation of how the framework works.

So, first we receive the http-request a new fresh state map is created and wrapped in a monad container, the route module will analyse the request and update the state map with the following info: action-handler and specific-route-interceptors.

The interceptors will be organized in a chain: :enter functions, action-handler, reverse :leave functions. This functions can and should interact with other resources, for example: database interface/driver.

Finally after all the computations are done the :response will be extracted from the last-container and return to the client.

Framework

Modules

Monads

“Monad is a simple and powerful mechanism for function composition that helps us to solve very common IT problems such as input/output, exception handling, parsing, concurrency and other. Application becomes less error prone. Code becomes reusable and more readable.”

And we use it to do exactly that: to add Failure / Success metadata to our internal wrapped state, our data flow unity.

Think of it as a container that’s compose by metadata plus its data value, and every function that returns the state map needs to wrapper it first providing the right binary direction Success or Failure.

This is done by the functions: xiana/ok and xiana/error respectively defined in xiana/core.clj.

The container will travel through the application and dictates how it will operates based on its binary direction values and the state map.

State

A simple map that is created for each HTTP request and represents the current state of the application in a given time, remember this structure is very volatile, i.e, will be updated quite often on the application’s life cycle.

The main modules that updates the state are:

  • Routes: Add information from the match route to the state map, for example: route’s action handler function.
  • Interceptors Add, consumes or remove information from the state map, more details on the Interceptors section.

In a nutshell the state is just a map that holds information in the application data flow cycle, the final step extracts the response value from the container and the remaining data is discarded, because each new request demands/creates a new clean state map.

Probably you’re under: why not just call it context? Go figure :), but putting jokes aside, this nomenclature will probably change in the near future.

Config

The configuration module controls the resources options and its specifications values that will be fetched at run time by the resources modules - server/database - in their initialization processes, by adding this layer of abstraction we provide a simple, robust, and extensible mechanisms to manage our configuration data.

So, we use the same and well know .edn file map that simplifies everything maintaining a certain degree of legibility.

The default development configuration file can be found at ./config/dev/config.edn.

It’s highly recommended to read the config.edn file and at least understand the basic syntax and its implications (options/specs), specially if you want to work on the core development of the framework.

The environment variable FRAMEWORK_EDN_CONFIG holds the file path definition and can be easily changed before the call of any auxiliary command, e.g:

env FRAMEWORK_EDN_CONFIG=./config/prod/config.edn lein test

Routes

This modules provides the routes manager and is a interfaces to the reitit routing match library.

The routes structure is defined and should use this module interfaces to register it, it’s also possible to update the routes at run time without the necessity of restart orders resources.

;; define root route handler
(def app-root-handler
  "App root route handler (index)."
  [state]
  (xiana/ok
   (-> state (assoc :response
                    {:status 200 :body "Hello Word!"}))))

;; define application's routes
(def app-routes
  [["/" {:action app-root-handler}]])

;; register application's routes
(route/reset app-routes)

We use reitit amazing library to handle the route matching process, and fetch the control/action handler function from its template map (the resulting map produced by route/match function), don’t worry if none is found default functions values will be used to update the state map, other important information for us are the specific route interceptors that will be merged and executed following one of the defined strategies, more on that latter…

Interceptors

An interceptor is a pair of unary functions. Each function is called with a state map and must return a wrapped container state map.

The log interceptor definition defines :enter/:leave functions to display the state.

(def log
  "Log interceptor.
  Enter: Print 'Enter:' followed by the complete state map.
  Leave: Print 'Leave:' followed by the complete state map."
  {:enter (fn [state] (println "Enter: " state) (xiana/ok state))
   :leave (fn [state] (println "Leave: " state) (xiana/ok state))})

All the :enter functions for all the registered interceptors will be organized as a chain and executed sequentially, the same happens with the :leave functions but they are executed in reverse other, and remember the route action-handler is invoked between them.

See the flow section image to a better understand of how this works.

It’s possible to set interceptors for all routes, they are called default-interceptors, but it’s also possible to set then peer route and also a combination of both, more on that letter.

But first, let’s see a more complete example:

(defn message
  "This interceptor creates a function that prints predefined message.
  Enter: Print an arbitrary message.
  Leave: Print an arbitrary message."
  [msg]
  {:enter (fn [state] (println msg) (xiana/ok state))
   :leave (fn [state] (println msg) (xiana/ok state))})

;; application default interceptors collection
(def app-interceptors
  [(interceptor/message "default-interceptors")])

;; default routes using interceptor peer route
(def app-routes
  [["/" {:action app-controller}]
   ;; example: override interceptors
   ["/override" {:action app-action
                 :interceptors [(interceptor/message "override")]}]

   ;; example: inside interceptors
   ["/inside" {:action app-action
               :interceptors {:inside [(interceptor/message "inside")]}}]

   ;; example: around interceptors
   ["/around" {:action app-action
               :interceptors {:around [(interceptor/message "around")]}}]

   ;; example: inside-around interceptors
   ["/around-and-inside"
    {:action
     :interceptors {:around [(interceptor/message "around")]
                    :inside [(interceptor/message "inside")]}}]])

We provide four strategies to combine the peer route interceptors with the defaults ones: override, inside, around, around-and-inside.

  • Override means just execute the interceptors defined in the route.

    ./resources/images/override.png

  • Around means execute the route interceptors around the default ones, i.e:

    ./resources/images/around.png

  • Inside means execute the route interceptors inside the default ones, i.e:

    ./resources/images/inside.png

  • Around & Inside means execute the route interceptors around and inside, i.e:

    ./resources/images/around-and-inside.png

So, that way we can add a degree of flexibility for the web application developers without loose the expressiveness and clarity.

Resources

Webserver

Responsible for starting the jetty server and register http-request function handler.

This function is just the ‘entry point’ that will apply route and interceptors function to the state, after everything is computed the :response is extract and return to the client side.

Database

Provides database interfaces and PostGreSQL driver to execute/commit our SQL statements.

Architecture

File-Tree

This is the current directory tree architecture, remember to update this block after every significant update!

.
├── framework
│   ├── app
│   │   ├── core.clj
│   │   ├── controllers
│   │   ├── helpers
│   │   ├── models
│   │   └── view
│   │       └── css
│   │           └── tailwind
│   │               ├── core.cljc
│   │               ├── helpers.cljc
│   │               ├── preparers.cljc
│   │               └── resolvers.cljc
│   ├── auth
│   │   └── hash.clj
│   ├── config
│   │   └── core.clj
│   ├── db
│   │   ├── core.clj
│   │   ├── postgresql.clj
│   │   └── sql.clj
│   ├── interceptor
│   │   ├── core.clj
│   │   ├── helpers.clj
│   │   ├── muuntaja.clj
│   │   ├── queue.clj
│   │   └── wrap.clj
│   ├── mail
│   │   └── core.clj
│   ├── route
│   │   ├── core.clj
│   │   └── helpers.clj
│   ├── session
│   │   └── core.clj
│   ├── state
│   │   └── core.clj
│   └── webserver
│       └── core.clj
└── xiana
    ├── commons.clj
    └── core.clj

Contributions

LICENSE

References

  1. https://clojuredocs.org/clojure.edn/read
  2. http://funcool.github.io/cats/latest/
  3. https://medium.com/@yuriigorbylov/monads-and-why-do-they-matter-9a285862e8b4
  4. http://pedestal.io/reference/interceptors

EOF

framework's People

Contributors

ianffcs avatar lambdart avatar narocath avatar yatagan avatar g-krisztian avatar wandersoncferreira avatar brahayan-dev avatar piotrkurnik avatar parallelo3301 avatar soulflyer avatar dimos-flexiana avatar

Stargazers

Roman 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.