This News Aggregator API implement some useful design patterns that helps to increase maintainability by reducing the coupled, whitch provides a good code structure. Therefore we start to talk about design patterns.
-
Singleton: is used by NestJS making every of its components a singleton across the module. Even modules are singletons by default, with the purpose that you can share the same instance of any provider between multiple modules effortlessly, so you don't need to create a new instance in runtime every time another component need a particular service.
-
Dependency Injection: Nestjs provides it out of the box, in order to enables you to replace dependencies without changing the class that uses them. So this might reduce the risk that you have to change a class just because one of its dependencies changed. To get this approach Nest delegate instantiation of dependencies to a IoC container, everytime you create a new component (provider) on your aplication, you make a registration process associating the token nameService with the class nameService. So when the Nest IoC container instantiates a Class (Let's call it Component A), it first looks for any dependencies. When it finds the dependency, it performs a lookup on the dependency token, which returns its respective class (let's call it Component B). Then Nest will then either create an instance of Component B, cache it, and return it, or if one is already cached, return the existing instance of Component B.
-
Decorator: The pattern is used heavily in the framework in order to attach new features and behaviors to already defined classes and methods. NestJS implement decorators using generics
createParamDecorator<T>((data, ctx) => ...)
. That means you can explicitly enforce type safety, the expression returns a function and can take a target, name and property descriptor as arguments. -
Strategy: NestJS has a library 'Passport' easily to integrate with, it works using the Strategy design pattern, separeting all its different implementation on strategies, so you can isolate the implementation details of an algorithm from the code that uses it.
-
Repository: Typeorm is database integration library that fits perfectly in NestJS, supports the repository design pattern, so each entity has its own repository. In simple words it deals with creating a set of data access services encapsulating CRUD operations. The implemented repositories are available out-of-the-box and can be obtained from the database connection.
-
Based on the features of our API, we can reduce the coupling of creating objects in ourReadItLaterService by applying the Factory method, Even we are working with simple data structure for Articles and Recommendations, they might easy increment and modify their estructure, and turn complex to hande the changes.
-
But for the moment the main problem that can be foreseen is the creation of the queryParameters' url for each News API. With that in mind, we observe that for the three News API, we are having 3 different formats of queryParams spected but all of then are builded using almost the same number of steps. Creational design pattern Builder looks like a posible solution that match with our problem.
-
Abstract Factory is not considered because we don't have related families of classes.
- We have commond services that handle request and responses for News APIs, we can easy get tempted with the idea of ​​apply Strategy pattern in order to set every varian of our services as an Strategy, but if we go deep in our code, We can check that all our Services have commond functions to carry out the work. Soon or later we could get into the needs of add a new News Aggregator. Applying Templete method Pattern we could easy provide a structure and the needs steps that the new News Service will follow!
Is the application of a solution that instead of meeting a solution, it creates a problems. Is important to have knowledge about antipatterns because as far as we know them, we can easily identify them, even remove them from our programs before they reach the production environment.
There are three classifications of anti patterns:
- Software Development AntiPatterns, which are based on the dramatically task of refactoring the initial software structure and designs planed in order to skip wrong considerations made on early software life cycle. efactoring approaches. This development antipatterns use various formal and informal refactoring approaches.
The structure changes because programmers learn constraints and approaches that alter the context of the coded solutions.
-
Software Architecture AntiPatterns, remarks the importance of software architecture as essential therefore its required a whole view point of the system, so its antipatterns involves the common problems and mistakes in the creation, implementation, and management of architecture.
-
Project Management AntiPatterns, it focuses on effective communication and interpersonal relationships among those involved in development, under the guidance of those responsible for improving the team work environment. Provides a guide to common bad experiences that allow you to improve your decision making.
First of all a dependency is component B (can be a class, a function, an interface, a method or even a field), that serve as a resource needed for component A to work. For this examples we are going to use 'InversifyJS' that provide us a IoC container for TypeScript.
The idea with dependency injection is that the component A should not have the responsibility to create its dependencies. Therefore component A is not allowed to use new
in order to instantiate a component B object.
Dependency injection suggest that dependencies should be passed already initialized over to the constructor. Example: Initials is dependent of Charmander class
class Initials implements {
private _fireInitial: Charmander;
public constructor(charmander : Charmander) {
this._fireInitial = charmander;
}
To reach this approach we use a Dependency Injection container better know as IoC container, is a framework for implementing automatic dependency injection.
This IoC container provibe an interface to retrieve an instance of each component with all its dependecies meet.
InversifyJS Doc: The classes are implementations of the interfaces that we just declared. All the classes must be annotated with the
@injectable
decorator.
Example:
export interface Pokemon {
quickAttack(): string;
chargedAttack(): string;
}
@injectable()
class Charmander implements Pokemon {
private quick_attack :
private charged_attack :
public constructor(
default : {
quick_attack: Scratch ,
charged_attack: Ember
})
{
this.quick_attack = default.quick_attack;
this.charged_attack = default.charged_attack;
}
public quickAttack() { return this.quick_attack.attack(); }
public chargedAttack() { return this.charged_attack.attack(); }
}
InversifyJS need to use the type as identifiers at runtime. We use symbols as identifiers but you can also use classes and or string literals. Example:
const TYPES = {
Pokemon: Symbol.for("Pokemon"),
};
export { TYPES };
Everytime you create a new component on your aplication, you register it in the container, that way the IoC container knows exactly how each componen is initialize.
Example:
const myContainer = new Container();
myContainer.bind<Pokemon>(TYPES.Pokemon).to(Charmander);
export { myContainer };
Finally we can resolve dependencies using our IoC Container.
import { myContainer, TYPES, Pokemon } "./example";
const charmander = myContainer.get<Pokemon>(TYPES.Pokemon);
Template Method: is used in order to solving a GOD Class antipattern, allowing to separate services by the News Aggregator APIs to request and defines a common number of steps to accomplish to carry out the work. Abstract class with common steps:
export abstract class AbstractClass {
public async templateMethod(params: ParamsNewsDto): Promise<NewsDto[]> {
const queryParams = this.createRequest(params);
const apiResponse = await this.consume(queryParams);
const articles = this.parseNews(apiResponse);
return articles;
}
protected abstract createRequest(params : ParamsNewsDto): string;
protected abstract async consume(queryParams : string): Promise<AxiosResponse>;
protected abstract parseNews(apiResponse: AxiosResponse): NewsDto[];
}
Each service implement createRequest, consume and parseNews methods, and a clientCode function is created to decouple the template method of the Client
clientCode function:
export const clientCode = async (abstractClass: AbstractClass,params: ParamsNewsDto): Promise<NewsDto[]> => {
return await abstractClass.templateMethod(params);
}
example of a call in the controller:
export class NewsController {
constructor( private readonly guardianService: GuardianService,) {}
@UseGuards(JwtAuthGuard)
@Get()
async getNews(@Query() query: ParamsNewsDto) {
let result: NewsDto[] = await clientCode(this.guardianService,query);
return result;
}
}
Builder: Previous code:
createRequest(params: ParamsNewsDto) {
let url = '';
url += params.hasOwnProperty('q') ? 'q=' + params.q : '';
if (params.hasOwnProperty('oncontent')) {...}
if (params.hasOwnProperty('onsection')) {...}
if (params.hasOwnProperty('fromdate')) {...}
if (params.hasOwnProperty('todate')) {...}
}
- In the previous code we can see that the creation of the queryParameters' url for each News API requires laborious step-by-step construction. For every news service, the request creation is builded using almost the same number of steps. Builder Pattern match with our intent of create different representations of our Request using the same construction code, at the same maintainable increases, so we can easely made changes on a concrete builder or in the construction steps.
Result code:
export interface Builder {
setQ(subquery: string): void;
setOnContent(subquery: string): void;
setOnSection(subquery: string): void;
setOnPage(subquery: string):void;
setKey(subquery: string, key: string | undefined):void;
}
export class TGQueryBuilder implements Builder {
private params: ParamsNewsDto;
private query: string = '';
constructor(params: ParamsNewsDto) {
this.params = params;
this.query+='';
}
public setQ(subquery: string): void {
this.query += this.params.hasOwnProperty('q') ? subquery + this.params.q : '';
}
public setOnContent(subquery: string): void {
if (this.params.hasOwnProperty('oncontent')) {
this.query +=
this.query.length > 3
? ' AND ' + this.params.oncontent + subquery
: 'q='+ this.params.oncontent + subquery;
}
}
export class BuilderDirector {
private builder: Builder;
public setBuilder(builder: Builder): void {
this.builder = builder;
}
public buildTGQuery(): void {
this.builder.setQ('q=');
this.builder.setOnContent('&query-fields=body');
this.builder.setOnSection('section=');
this.builder.setOnPage('page=');
this.builder.setKey('&api-key=',process.env.TG_KEY);
}
}
-
GOD Class: A first it seem one service could be enough for handle request on two News Aggregator APIs, then when you have to add a new News Aggregator API thinks comes pretty difficult and also if you want to change something for your previous services the panorama gets dark! So, separeting every services by their respectives functions for a single News API clarify any maintenance attempt.
-
Spaghetti Code: In order to add new features like adding articles or submitting recommendations, the two previous modules looked very messy and even if create one service for those features could be the easiest ways, the final decition was refactor the project structure taking the useful parts of code and separate it all in fourt modules by their own bussines logic every one of then.
News Module :
- Handle searches on NewsAPI, The Guardian and The New York Times' APIs.
Auth Module :
- Handle login authentication and the authorization using JWT token for every endpoint of the API.
Users Module :
- Handle the creation of an user account.
ReadItLate Module:
- Handle saving and getting users' articles.
- Allows recommend article to another user, also users get the recommendations received.
VERSION | |
---|---|
Node | ^12.16.3 |
Typescript | ^3.7.4 |
Nestjs | ^7.0.0 |
@nestjs/config | ^0.5.0 |
psql (PostgreSQL) | ^9.4.4 |
Run the DBscript.sql file will create "newsdb"
\i DBscript.sql
important: an Admin user will be created! test login with it!
{ "email": "admin@admin.com", "password": "admin" }
Endpoint | Description | AuthKey |
---|---|---|
POST /users |
Create user account | No required |
GET /users |
Get all users registered | No required |
POST /login |
Authenticate user and retrieve an auth key | No required |
GET /news |
Search news on NewsAPI, The Guardian and The New York Times' APIs. | Required |
POST /articles |
Save and article in the user account | Required |
GET /articles |
Retrieve the articles of the user | Required |
POST /recommendations |
Send a recommendation to another user | Required |
GET /recommendations |
Get the recommendations received | Required |
Try to login using admin account created by default in the DBscript!
You can check endpoints' tests here:
http://localhost:3000/news
PARAMETER | DESCRIPTION | EXAMPLE |
---|---|---|
api | Define the api in which you want to search for news or if you want to search using all. options: nyt tg news all |
api=nyt |
q | Request content containing this free text. Supports AND, OR and NOT operators. | q=pizza |
oncontent | This query will look for matches in the body of the articles | content=pineapple |
onsecton | Return only content in the sections | onsecton=food |
fromdate | Return only content published on or after that date | fromdate=2020-05-18 |
todate | Return only content published on or before that date | todate=2020-06-12 |
onpage | Return only the result set from a particular page, must be a number | onpage=1 |
Examples :
http://localhost:3000/news?api=nyt&q=pizza&oncontent=pineapple&onsection=food&fromdate=2020-05-18&todate=2020-06-12
Search only on The Guardians source
http://localhost:3000/news?q=music&api=tg