Giter Club home page Giter Club logo

three-layer's Introduction

Logo CircleCI

three-layer

This package is aimed at being a modern, production-level, batteries-included starting template for writing web servers with Haskell on backend and Elm on frontend. It follows the Three Layer Cake. architecture pattern.

Haskell libraries used in here:

Detailed approach description

This section contains more detailed description of the chosen architecture and our particular implementation of it.

Application environment

Data type for the runtime environment for the whole application is defined in the Lib/App/Env.hs module. It contains various fields required for the application processing, like database pool, JWT secret, logger, etc. It also has instance of custom Has typeclass which tells how to extract different parts of the application. This is done to achieve the following purposes:

  1. Specify in the constraints what parts of the environment you need.
  2. Introduce more modularity when multiple different environments are implemented.

Environment initialisation is happening in the Lib.hs module.

Application errors

Module Lib/App/Error.hs contains exhaustive list of all errors that application can throw. This module provides convenient layer between human-readable error names and HTTP error codes. It also contains useful utilities for throwing errors and for formatting CallStack of errors.

Application monad

Main application monad can be found in the Lib/App/Monad.hs module.

Database

This template uses PostgreSQL database and contains helper wrappers around functions from the postgresql-simple library to integrate smoother with our own monad. See Lib/Db/Functions.hs for more details.

Effects

All new effects (like sending an email. storing the file, etc.) should be added to the Lib/Effects/ directory.

three-layer's People

Contributors

2mol avatar arbus avatar chshersh avatar lucazulian avatar rashadg1030 avatar tejassc avatar vrom911 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  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  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  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

three-layer's Issues

Readme tweaks

  • One typo: just after proto-lens: Protobugf
  • The link for co-log points to relude
  • Also, I find the description for co-log ill-suited. I would assume the intended audience is has more an engineering mindset, but "composable contravariant comonadic logging library" seems straight out of an academic paper. If someone is comfortable with all of that, they are probably too busy writing another cool library using their latest paper, for other people, it's just off-putting.
    How about composable and configurable logging library?

Cosmetic improvements

Beginner friendly

  • Add -Wall to tests and executables
  • Pattern-matching in String jwtId
  • Move Jwt from Util.* to Lib.Core.Jwt
  • Move Password from Util.* to Lib.Core.Password
  • Move database-related functions from Util.App to Lib.Db
  • Move error-related functions from Util.App to Lib.App.Error
  • Add perform to Lib.Db
  • Add Makefile with ghcid command
  • Left HeaderDecodeError -> throwError err404 should be err401
  • Extend default-extensions
  • Update stack.yaml with -fhide-src-paths
  • Deriving order for newtypes
  • Split Lib.Util.App module
  • Refactor errors to new model in protobuf and enum and with toHttpError

Medium friendly

  • JwtSpec and PasswordSpec
  • Add ekg
  • Add Date data type to protobuf
  • Add newtype Seconds to Lib.Time module and refactor existing functions

Hard friendly

  • Server.Types module with convenient type aliases
  • Decide on katip vs. monad-logger, migrate to released katip version if chosen, remove forked repo

Cloning & renaming tool

We need tool which will copy three-layer repository and rename all modules and directories according to provided names of new project and module prefix.

What should be changed?

  • Name of the project inside package.yaml
  • GitHub user and package name inside package.yaml
  • Name of Lib/ folder inside src/ to new prefix
  • Every module name
  • Every Lib.* import inside each module

Specification

  1. Tool takes two CLI arguments: name of new project and module prefix.
  2. Should be implemented as separate executable/package inside three-layer repository and should clone everything from repo except tool itself.
  3. New project should be buildable without errors and warnings after using the tool.
  4. ★ Implement automated tests for this executable.

Updates to some files

  • Use newer .stylish-haskell.yaml
  • Update .gitignore
  • Add {-# INLINE #-} to some small functions
  • Use Type -> Type in Server.Types
  • Use bcrypt extension in psql, use WHERE email = LOWER(?)
  • Make fields strict in all data types
  • Update comment to User data type mentioning SQL table

[Question] How to remove 'hoistServer'

I've been trying to make a simpler version of three-layers, which doesn't have logging but I'm not able to find out what I need to put in place of hoistServer from bellow?

server :: AppEnv -> Server Api
server env = hoistServer
(Proxy @Api)
(runAppAsHandler env)
(toServant authServer)
application :: AppEnv -> Application
application env = serve
(Proxy @Api)
(server env)

This is what I have so far but also unsure if I should be passing my AppEnv through to my httpServer:

type AppApi = ToApi HttpSite

-- | These are the routes to the http app
data HttpSite route = HttpSite
    { -- | Post Hello
      helloRoute :: route
        :- "hello"
        :> Post '[JSON] HelloRes
    } deriving (Generic)

newtype HelloRes = HelloRes { msg :: String }
  deriving Generic

instance ToJSON HelloRes

httpServer :: HttpSite AppServer
httpServer = HttpSite
    { helloRoute = helloHandler
    }

-- | update the request count by one and return
helloHandler :: ( MonadStats m ) => m HelloRes
helloHandler = do
    reqCount <- getStats
    let newReqCount = (reqCount + 1)
    putStats newReqCount
    pure $ HelloRes (show newReqCount)

httpApp :: AppEnv -> Application
httpApp env = serve
    (Proxy @AppApi)
    httpServer -- ?? should I pass the env

right now I get the error:

Couldn't match type ‘Handler HelloRes’ with ‘HttpSite AppServer’
Expected type: Server ("hello" :> Post '[JSON] HelloRes)

I've been trying to look through the docs for servant but can't find anything that helps 😸

Add type synonym for `Jwt` Effect

Should these be turned into a type synonym?

:: (MonadIO m, MonadReader r m, Has JwtSecret r)

:: (MonadIO m, MonadReader r m, Has JwtSecret r)

Like:
type WithJwt r m = (MonadIO m, MonadReader r m, Has JwtSecret r)
Also, I've noticed in some modules the type variable r in these constraint synonyms is replaced with env. Which one is better or does it depend?

How to integrate servant-auth for JWT based authentication?

Thank you for this starter project! Being relatively new to Haskell, the structure seems clear and easy to follow with nice separation of concerns.

I would like to integrate the servant-auth library for easy JWT authentication, but whatever I try, I do not get it to compile. Could maybe somebody give me an indication about the general approach that would be necessary?
What I tried:

Maybe I have approached this all wrong? How would you use JWT authentication without repeating authentication logic for each handler?

Refactor Effects approach

Current approach with MonadReader AppEnv m is not very convenient. It doesn't allow convenient mocks. So this type class signature is not perfect:

class (MonadReader AppEnv m, MonadError AppError m, MonadIO m) => MonadUser m where

It's better to have:

class MonadUser m where
instance MonadUser App where

We can go further and introduce Has* type classes. So we will have:

data AppEnv = AppEnv
  { dbPool    :: Pool Connection
  , sessions  :: MVar (Map Text Session)
  , jwtSecret :: Text
  , timings   :: IORef (Map Text Distribution)
  , ekgStore  :: Store
  }

class HasDbPool r
class HasSessions r
class HasJwtSecret r
...

And instead of having type like this:

getUserDetailImpl 
  :: ( MonadReader AppEnv m
     , MonadError AppError m
     , MonadIO m
     ) => Text -> m UserDetail

We will have type:

getUserDetailImpl 
  :: ( MonadReader r m, HasDbPool r
     , MonadError AppError m
     , MonadIO m
     ) => Text -> m UserDetail

So it's completely clear from type that this function uses only DB.

Make naming more consistent

  • Route suffix for endpoints
  • Handler suffix for endpoint implementations
  • Impl suffix for effects implementation

Simplify failsWith function

-- | Checks whether action fails and returns given error.
failsWith :: (Show a) => App a -> AppErrorType -> AppEnv -> Expectation
failsWith app err env = runAppAsIO env app >>= \case
Left e@AppError{..} ->
if appErrorType == err
then appErrorType `shouldBe` err
else expectationFailure $
"Expected 'Failure' with: " <> show err <> " but got: " <> show e
Right a -> expectationFailure $
"Expected 'Failure' with: " <> show err <> " but got: " <> show a

The Left case can be simplified to appError `shouldBe` err.

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.