Just another proof of concept about how build distributed applications.
This is a quite simple application where we can create a rest API based on an initial CSV file provided by the user.
- Node ( >= 8.x) + Typescript
- Express - Web server
- MongoDB - to store data from our services (Provided by Scalingo)
- Redis - as a queue based (Provided by Scalingo)
- Swagger - For API documentation
- Heroku - As our cloud platform
Each module has a Core, which contains entities
that are basically business rules. Also we have a service
layer to handle everything that isn't related to the Core such validations, resources and so on. By providing an output such as api
we'll have the normal bits such an application server and it routes+controllers with the only difference to handle logic with use cases
instead, to avoid put garbage on our controllers that shouldn't know anything about our application. Finally we have our repositories
, which will deal with persistence through our services and use cases.
├── api # API specifics such web server, routes, etc.
├── common # Helpers & Utilities
├── config # Configuration
├── core # Where the business logic lives
│ ├── entities # Our domain classes
│ └── usecases # Our business rules
├── repositories # Our persistence layer
└── services # Our service layer
// index.js
(async () => {
const config = require('config');
const App, { AppConfig } = require('/Application');
// Load the configuration
const appConfig: AppConfig = config.get('application')
const repoConfig: DatabaseSettings = config.get('database');
// Add repository
const repository: Repository = require('/repositories/TaskRepository');
const app: App = new App({
...appConfig,
repository: new repository(repoConfig)
});
// Add use cases to event listeners
events.on('add_task', require('/usecase/AddTask').['add']);
// Start the application
await app.start();
return app;
})();
// usecase/AddTask.js
const Task = require('/entities/Task');
const Application = require('/Application');
class AddTask {
async add(entity: Task): Task| AppError {
const task: Task = new Task(entity);
if(task.isValid()) {
await Application.repository.save(task);
return task;
}
throw new Error(...)
}
}
// entities/Task.js
class Task {
constructor(public title: string = '') {}
isValid(): boolean {
return this.title && this.title.trim();
}
}
// repositories/TaskRepository.js
class TaskRepository extends Read<Task>, Write<Task> {
constructor(private db: DatabaseSettings) {}
getAll() { ... }
save() { ... }
}