GithubHelp home page GithubHelp logo

jbuget / nodejs-clean-architecture-app Goto Github PK

View Code? Open in Web Editor NEW
1.5K 50.0 264.0 1.38 MB

A simple API built with Hapi.js that follows DDD + Clean Architecture principles

Home Page: https://hapijs-v17-app.herokuapp.com/users

License: MIT License

JavaScript 100.00%

nodejs-clean-architecture-app's Introduction

A basic Hapi.js API following Clean Architecture principles

Getting started (< 2mn)

git clone [email protected]:jbuget/nodejs-clean-architecture-app.git
cd nodejs-clean-architecture-app
npm install
npm test
npm start

In a browser, open http://localhost:3000/hello.

Domain Driven Architectures

Software design is a very hard thing. From years, a trend has appeared to put the business logic, a.k.a. the (Business) Domain, and with it the User, in the heart of the overall system. Based on this concept, different architectural patterns was imaginated.

One of the first and main ones was introduced by E. Evans in its Domain Driven Design approach.

DDD Architecture

Based on it or in the same time, other applicative architectures appeared like Onion Architecture (by. J. Palermo), Hexagonal Architecture (by A. Cockburn) or Clean Architecture (by. R. Martin).

This repository is an exploration of this type of architecture, mainly based on DDD and Clean Architecture, on a concrete and modern JavaScript application.

DDD and Clean Architecture

The application follows the Uncle Bob "Clean Architecture" principles and project structure :

Clean Architecture layers

Schema of flow of Clean Architecture

Project anatomy

app 
 └ lib                              → Application sources 
    └ application                   → Application services layer
       └ security                   → Security tools interfaces (ex: AccessTokenManager.js, to generate and decode OAuth access token)
       └ use_cases                  → Application business rules 
    └ domain                        → Enterprise core business layer such as domain model objects (Aggregates, Entities, Value Objects) and repository interfaces
    └ infrastructure                → Frameworks, drivers and tools such as Database, the Web Framework, mailing/logging/glue code etc.
       └ config                     → Application configuration files, modules and services
          └ service-locator.js      → Module that manage service implementations by environment
       └ orm                        → Database ORMs middleware (Sequelize for SQLite3 or PostgreSQL, Mongoose for MongoDB)
          └ mongoose                → Mongoose client and schemas
          └ sequelize               → Sequelize client and models
       └ repositories               → Implementation of domain repository interfaces
       └ security                   → Security tools implementations (ex: JwtAccessTokenManager)
       └ webserver                  → Hapi.js Web server configuration (server, routes, plugins, etc.)
          └ oauth                   → Hapi.js authentication strategies and schemas
          └ server.js               → Hapi.js server definition
    └ interfaces                    → Adapters and formatters for use cases and entities to external agency such as Database or the Web
       └ controllers                → Hapi.js route handlers
       └ routes                     → Hapi.js route definitions
       └ serializers                → Converter objects that transform outside objects (ex: HTTP request payload) to inside objects (ex: Use Case request object)
 └ node_modules (generated)         → NPM dependencies
 └ test                             → Source folder for unit or functional tests
 └ index.js                         → Main application entry point

Flow of Control

Schema of flow of Control

The Dependency Rule

The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity.

src. https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html#the-dependency-rule

Server, Routes and Plugins

Server, routes and plugins can be considered as "plumbery-code" that exposes the API to the external world, via an instance of Hapi.js server.

The role of the server is to intercept the HTTP request and match the corresponding route.

Routes are configuration objects whose responsibilities are to check the request format and params, and then to call the good controller (with the received request). They are registered as Plugins.

Plugins are configuration object that package an assembly of features (ex: authentication & security concerns, routes, pre-handlers, etc.) and are registered at the server startup.

Controllers (a.k.a Route Handlers)

Controllers are the entry points to the application context.

They have 3 main responsibilities :

  1. Extract the parameters (query or body) from the request
  2. Call the good Use Case (application layer)
  3. Return an HTTP response (with status code and serialized data)

Use Cases

A use case is a business logic unit.

It is a class that must have an execute method which will be called by controllers.

It may have a constructor to define its dependencies (concrete implementations - a.k.a. adapters - of the port objects) or its execution context.

Be careful! A use case must have only one precise business responsibility!

A use case can call objects in the same layer (such as data repositories) or in the domain layer.

nodejs-clean-architecture-app's People

Contributors

jbuget avatar sroccaserra 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nodejs-clean-architecture-app's Issues

Domain Services

What is services in enterprise_business_rules (UpdateAnalytics.js)?

Proposal: use inheritance instead of composition for the repository pattern

I think having chosen composition for the repository pattern is a problem here:

UserRepository.js

class UserRepository {
  constructor(repository) {
    this.repository = repository;
  }

  persist(userEntity) {
    return this.repository.persist(userEntity);
  }

  // ...

}

UserInMemoryRepository.js

// Nothing to see here, no explicit dependency :(

UserController.js

  // ...
  const userRepository = new UserRepository(new UserRepositoryInMemory());
  //...

It has at least three inconvenients:

  • having to explicitely delegate every method call to this.repository
  • breaking the plugin architecture (here a change in the outer circle code would require to change the inner circle code)
  • having no explicit dependency to the UserRepository class in the in memory & sqlite implementations

I propose to use inheritance instead:

UserRepository.js

class UserRepository {
  persist(userEntity) {
    // This optional generic implementation is obviously meant to be overriden.
    // See also Python's NotImplementedError and Smalltalk's subclassResponsibility.
    throw new Error('ERR_METHOD_NOT_IMPLEMENTED');
  }

  // ...

}
// Explicit dependency here :)
const UserRepository = require('../path/to/UserRepository')

class UserRepositoryInMemory extends UserRepository {
  persist(userEntity) {
    // Real implementation here
    // ...
  }
}

UserController.js

  // This initiation could be done in a composition root,
  // which would be the only place where things could get a little dirty ;)
  const userRepository = new UserRepositoryInMemory();
  
  // ...

Interface UserRepositorySQLite.js depends on sequelize (frameworks_drivers)

Isn't that a decency from an inner layer (interface_adapters) to an outer layer (frameworks_drivers)?

// UserRepositorySQLite.js

const sequelize = require('../../frameworks_drivers/database/sequelize');

Couldn't get myself to a way of inverting this dependency, but would be grateful if anyone does :)

How to apply mongoose

Hello @jbuget I think that this example is the most clear one but, I'm having difficulties about adding mongoose properly.

I think it would be useful if we expand this example in another branch with a mongoose or ODM example.

Controllers should not require directly the service locator

Service locator depends on environment and configuration modules/files. Thus its place is in infrastructure package.

For now, service locator is required directly by the controllers. This dependance breaks the golden rule that forbid an inner layered component to directly acccess outer layered component.

A solution may be to inject the service in every Hapi request. It then will be used by (for example, in a route handler/controller) request.app.serviceLocator.userRepository.

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.