O tym jak działa kontroler, czym powinien się zajmować i dlaczego nie powinien realizować zbyt wiele. W dzisiejszym wpisie powiem o tym dlaczego nie warto budować wielkich kontrolerów będących boskimi klasami.
@Controller('incomes')
export class IncomesController {
constructor(private readonly incomesService: IncomesService) {}
@Post('calculate/year')
async calculateYearIncome(@Body() request: CalculateYearIncomeRequest): Promise<YearIncomeResponse> {
const yearIncome = await this.incomesService.calculateYearIncome(request as CalculateYearIncomeDTO);
const response = YearIncomeResponseMapper.mapIncomeEntity(yearIncome);
return response;
}
}
Mamy tutaj przyjęcie i automatyczną walidację danych, wywołanie pewnej logiki biznesowej i użycie jej wyniku do sformatowania obiektu wyjściowego (ang. response object). Oczywistym jest także fakt, iż postać tej metody będzie się różnić w zależności od użytej technologii czy frameworka. Niejednokrotnie może okazać się, iż konieczne jest ręczne wywołanie pewnej metody walidującej, czy też sam obiekt wyjściowy może tworzyć się automatycznie. @Controller('incomes')
export class IncomesController {
constructor(private readonly incomesService: IncomesService) {}
@Post('calculate/year')
async calculateYearIncome(@Body() form: CalculateYearIncomeForm): Promise<YearIncomeResponse> {
form.validate();
const dto = CalculateYearIncomeDTOMapper.map(form.getData());
const yearIncome = await this.incomesService.calculateYearIncome(dto);
const response = YearIncomeResponseMapper.mapIncomeEntity(yearIncome);
return response;
}
}
@Controller('incomes')
export class IncomesController {
constructor(
private readonly incomeRepository: IncomeRepository,
private readonly invoiceRepository: InvoiceRepository
) {}
@Post('calculate/year')
async calculateYearIncome(@Body() request: YearIncomeRequest): Promise<Income> {
const { year, companyId } = request;
const invoices = await this.invoiceRepository.findByYearAndType(
year,
invoiceType: InvoiceTypeEnum.SELL,
companyId
);
const incomeValue = invoices.reduce((income: number, invoice: Invoice) =>
income += invoice.netValue, 0);
let income = await this.incomeRepository.findYearIncome(year, companyId);
if (income) {
income.value = incomeValue;
return this.incomeRepository.save(income);
}
income = IncomeFactory.createYearIncome({ year, companyId });
return this.incomeRepository.save(income);
}
}
SOLID (Single responsibility principle, Open/closed principle, Liskov substitution principle, Interface segregation principle, Dependency inversion principle), czyli pięć zasad programowania obiektowego, które każdy powinien przestrzegać.
Sprawdź ten wpisAby projektować dobrą architekturę oprogramowania trzeba najpierw zaznajomić się z jej przesłankami, rodzajami, a także sposobami wdrażania. Architektura nie jest przecież czymś stałym, co wszędzie implementowane jest w jednakowy sposób.
Sprawdź ten wpisCzym jest podejście composition over inheritance? Dzisiaj o tym, dlaczego każdy powinien przemyśleć czy dziedziczenie klas w jego projekcie jest naprawdę potrzebne. Prawdopodobnie powinieneś ograniczyć dziedziczenie klas na rzecz ich kompozycji.
Sprawdź ten wpisCommand-Query Separation, czyli zasada o rozdzielaniu zadań metod tak, aby były jedynie komendami lub zapytaniami. Jeżeli Twoja metoda realizuje logikę i zwraca wyniki, lub podczas pobierania danych wykonuje side taski, to łamie ona CQS.
Sprawdź ten wpisOmawiam dzisiaj mało znaną zasadę, której stosowanie skutkuje posiadaniem łatwo utrzymywalnego i testowalnego kodu. Mowa o prawie demeter, które w najprostszym ujęciu zakłada, że obiekty powinny operować jedynie na najbliższym im otoczeniu.
Sprawdź ten wpisFanatyk czystego i prostego kodu. Zwolennik podejść DDD oraz Modular Monolith. Na codzień pracuje jako programista i architekt. Po godzinach spełnia się w projektach open source, udziela się na blogach oraz czyta książki o kosmosie i astrofizyce.