
API communication without vs. with Node.js middleware
Create templates for Route, Control, Service, Repository layers and DI by yarn command
The middleware architecture overview
Relationship diagram between object types and layers
import { IsNotEmpty, IsString } from "class-validator";
import { ParsedQs } from "qs";
export class GetInspectionLotQueryDTO {
@IsNotEmpty()
@IsString()
lotID: string;
constructor(query: ParsedQs) {
this.lotID = query.lotID as string;
}
}
export class GetInspectionLotResDTO {
id: string;
plant: string;
constructor(data: {
id: string;
plant: string;
}) {
this.id = data.id;
this.plant = data.plant;
}
}
import { Exclude, Expose, Transform, Type } from "class-transformer";
@Exclude()
export class InspectionLot {
@Expose({ name: "InspectionLot" })
id: string;
@Expose({ name: "Plant" })
plant: string;
}
export interface InspectionLotResDAO {
InspectionLot: string;
InspectionLotObjectText: string;
InspectionLotActualQuantity: string;
InspectionLotQuantityUnit: string;
InspectionLotStartDate: string;
InspectionLotEndDate: string;
to_InspectionLotWithStatus: { InspLotStatusInspCompleted: string };
}
import { injectable } from "inversify";
import { InspectionLotResDAO } from "~/s4/dao/inspectionLotResDAO";
import { UsageDecisionReqDAO } from "~/s4/dao/usageDecisionReqDAO";
import { InspectionLot } from "~/s4/model/inspectionLot";
import { UsageDecision } from "~/s4/model/usageDecision";
import { createAPIClientDefault } from "~/s4/repository/index";
import { InspectionRepository } from "~/s4/service/inspectionRepository";
import { AuthData } from "~/type/authData";
import { fromDAO2Model } from "~/utils/fromDAO2Model";
const client = createAPIClientDefault(
"/sap/opu/odata/sap/API_INSPECTIONLOT_SRV"
);
@injectable()
export class InspectionRepositoryImpl implements InspectionRepository {
public async findInspectionLotByLotID(lotID: string): Promise<InspectionLot> {
const res = await client.get(
`/A_InspectionLot('${lotID}')?$expand=to_InspectionLotWithStatus`
);
const dao: InspectionLotResDAO = res.data.d;
return fromDAO2Model(InspectionLot, dao);
}
public async saveUsageDecision(
authData: AuthData,
usageDecision: UsageDecision
😞 Promise<void> {
const body: UsageDecisionReqDAO = {
d: {
InspectionLot: usageDecision.lotID,
InspLotUsageDecisionLevel: usageDecision.usageDecisionLevel,
InspectionLotQualityScore: usageDecision.qualityScore,
InspLotUsageDecisionCatalog: usageDecision.catalogID,
SelectedCodeSetPlant: usageDecision.plant,
InspLotUsgeDcsnSelectedSet: usageDecision.usageDecisionSelectedSet,
InspLotUsageDecisionCodeGroup: usageDecision.catalogName,
InspectionLotUsageDecisionCode: usageDecision.usageDecisionCode,
ChangedDateTime: usageDecision.changedDate,
},
};
await client.post(`/A_InspLotUsageDecision`, body, {
headers: {
"Content-Type": "application/json",
"x-csrf-token": authData.xCSRFToken,
Cookie: authData.cookie,
},
});
}
}
import { inject, injectable } from "inversify";
import { TYPES } from "~/di/types";
import { PostUsageDecisionBodyDTO } from "~/s4/dto/postUsageDecisionBodyDTO";
import { InspectionLot } from "~/s4/model/inspectionLot";
import { UsageDecision } from "~/s4/model/usageDecision";
import { CatalogRepository } from "~/s4/service/catalogRepository";
import { InspectionRepository } from "~/s4/service/inspectionRepository";
import { InspectionService } from "~/s4/service/inspectionService";
@injectable()
export class InspectionServiceImpl implements InspectionService {
private inspectionRepo: InspectionRepository;
private catalogRepo: CatalogRepository;
constructor(
@inject(TYPES.InspectionRepository)
inspectionRepo: InspectionRepository,
@inject(TYPES.CatalogRepository)
catalogRepo: CatalogRepository
) {
this.inspectionRepo = inspectionRepo;
this.catalogRepo = catalogRepo;
}
public async getInspectionLot(lotID: string): Promise<InspectionLot> {
return await this.inspectionRepo.findInspectionLotByLotID(lotID);
}
public async postUsageDecision(
bodyDTO: PostUsageDecisionBodyDTO
😞 Promise<void> {
const authData = await this.inspectionRepo.findAuthData();
const usageDecision = new UsageDecision({ ...bodyDTO });
const inspectionResults =
await this.inspectionRepo.findInspectionResultsByLotID(bodyDTO.lotID);
usageDecision.qualityScore =
usageDecision.calcQualityScore(inspectionResults);
usageDecision.usageDecisionCode = usageDecision.addUsageDecision();
await this.inspectionRepo.saveUsageDecision(authData, usageDecision);
}
}
import { inject, injectable } from "inversify";
import { TYPES } from "~/di/types";
import { InspectionController } from "~/s4/controller/inspectionController";
import { GetInspectionLotQueryDTO } from "~/s4/dto/getInspectionLotQueryDTO";
import { GetInspectionLotResDTO } from "~/s4/dto/getInspectionLotResDTO";
import { UsageDecision } from "~/s4/model/usageDecision";
import { InspectionService } from "~/s4/service/inspectionService";
import { ControllerMethod } from "~/type/controllerMethod";
import { HttpStatusCode } from "~/type/httpStatusCode";
import { validateDTO } from "~/utils/validateDTO";
@injectable()
export class InspectionControllerImpl implements InspectionController {
private service: InspectionService;
constructor(
@inject(TYPES.InspectionService) inspectionService: InspectionService
) {
this.service = inspectionService;
}
public getInspectionLot: ControllerMethod = async (req, res) => {
const queryDTO = new GetInspectionLotQueryDTO(req.query);
await validateDTO(queryDTO);
const lot = await this.service.getInspectionLot(queryDTO.lotID);
const resDTO = new GetInspectionLotResDTO(lot);
res.send(resDTO);
};
public postUsageDecision: ControllerMethod = async (req, res) => {
const bodyDTO = new UsageDecision(req.body);
await validateDTO(bodyDTO);
await this.service.postUsageDecision(bodyDTO);
res.status(HttpStatusCode.CREATED).send({ message: "Post success" });
};
}
import { validate } from "class-validator";
import { HTTP400Error } from "~/utils/errors";
export const validateDTO = async (object: object): Promise<void> =>
validate(object).then((res) => {
if (res.length > 0) {
throw new HTTP400Error(
res[0].constraints[Object.keys(res[0].constraints)[0]]
);
}
});
import { Router } from "express";
import { myContainer } from "~/di/inversify.config";
import { TYPES } from "~/di/types";
import { InspectionController } from "~/s4/controller/inspectionController";
import { asyncWrapper } from "~/utils/asyncWrapper";
const controller = myContainer.get<InspectionController>(
TYPES.InspectionController
);
const inspectionRoute = Router();
inspectionRoute.get(
"/lot",
asyncWrapper(async (req, res) => await controller.getInspectionLot(req, res))
);
inspectionRoute.post(
"/usage-decision",
asyncWrapper(async (req, res) => await controller.postUsageDecision(req, res))
);
export { inspectionRoute };
import { NextFunction, Request, Response } from "express";
type RouteHandler = (
req: Request,
res: Response,
next: NextFunction
) => Promise<void>;
type AsyncWrapper = (handler: RouteHandler) => RouteHandler;
export const asyncWrapper: AsyncWrapper =
(handler) => async (req, res, next) => {
try {
await handler(req, res, next);
} catch (error) {
next(error);
}
};
import { AxiosError } from "axios";
import { Application, NextFunction, Request, Response } from "express";
import { HttpStatusCode } from "~/type/httpStatusCode";
import { HTTP400Error } from "~/utils/errors";
interface ErrorResponse {
name: string;
httpCode: number;
isOperational: boolean;
message: string;
stack: string | undefined;
}
export const configureErrorHandler = (app: Application): void => {
app.use(
(error: Error, _: Request, res: Response, next: NextFunction): void => {
if (res.headersSent) {
return next(error);
}
if (error instanceof AxiosError) {
const httpCode =
error.response?.status || HttpStatusCode.INTERNAL_SERVER;
const axiosError: ErrorResponse = {
name: error.code || "AxiosError",
httpCode: httpCode,
isOperational: false,
message:
error.response?.data?.error?.message?.value ||
"An error occurred while processing the request.",
stack: error.stack,
};
res.status(httpCode).json(axiosError);
} else if (error instanceof HTTP400Error) {
const errorJson: ErrorResponse = createErrorResponse(
error,
error.httpCode
);
res.status(error.httpCode).json(errorJson);
} else {
const errorJson: ErrorResponse = createErrorResponse(
error,
HttpStatusCode.INTERNAL_SERVER
);
res.status(HttpStatusCode.INTERNAL_SERVER).json(errorJson);
}
}
);
};
import { Application } from "express";
import { userRoute } from "~/externalPlatform1/route/userRoute";
import { inspectionRoute } from "~/s4/route/inspectionRoute";
export const configureRoute = (app: Application): void => {
app.use("/external-platform1/user", userRoute);
app.use("/s4/inspection", inspectionRoute);
};
import express, { Application } from "express";
import http from "http";
import "reflect-metadata";
import { configureErrorHandler } from "~/server-config/errorHandler";
import { configureLogging } from "~/server-config/logging";
import { configureRequestParser } from "~/server-config/requestParser";
import { configureRoute } from "~/server-config/route";
import { configureSecurity } from "~/server-config/security";
const app: Application = express();
const port: number = 3000;
configureLogging(app)
configureRequestParser(app)
configureRoute(app);
configureSecurity(app);
configureErrorHandler(app);
const server = http.createServer(app);
server.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
Inter-layer relationships
export class InspectionServiceImpl implements InspectionService {
private inspectionRepo: InspectionRepository;
private catalogRepo: CatalogRepository;
constructor() {
this.inspectionRepo = new InspectionRepositoryImpl();
this.catalogRepo = new CatalogRepositoryImpl();
}
}
export class InspectionServiceImpl implements InspectionService {
private inspectionRepo: InspectionRepository;
private catalogRepo: CatalogRepository;
constructor(
inspectionRepo: InspectionRepository,
catalogRepo: CatalogRepository
) {
this.inspectionRepo = inspectionRepo();
this.catalogRepo = catalogRepo();
}
}
import { GetInspectionLotsQueryDTO } from "~/s4/dto/getInspectionLotsQueryDTO";
import { PostInspectionQlResultBodyDTO } from "~/s4/dto/postInspectionQlResultBodyDTO";
import { PostInspectionQnResultBodyDTO } from "~/s4/dto/postInspectionQnResultBodyDTO";
import { PostUsageDecisionBodyDTO } from "~/s4/dto/postUsageDecisionBodyDTO";
import { CharacteristicCode } from "~/s4/model/characteristicCode";
import { InspectionCharacteristic } from "~/s4/model/inspectionCharacteristic";
import { InspectionLot } from "~/s4/model/inspectionLot";
export interface InspectionService {
getInspectionLot(lotID: string): Promise<InspectionLot>;
getInspectionLots(
queryDTO: GetInspectionLotsQueryDTO
😞 Promise<InspectionLot[]>;
getInspectionCharacteristics(
lotID: string
😞 Promise<InspectionCharacteristic[]>;
getCharacteristicCodes(catalogName: string): Promise<CharacteristicCode[]>;
postInspectionQnResult(bodyDTO: PostInspectionQnResultBodyDTO): Promise<void>;
postInspectionQlResult(bodyDTO: PostInspectionQlResultBodyDTO): Promise<void>;
postUsageDecision(bodyDTO: PostUsageDecisionBodyDTO): Promise<void>;
}
const controller = new InspectionControllerImpl(
new InspectionServiceImpl(
new InspectionRepositoryImpl(),
new CatalogRepositoryImpl()
)
);
const inspectionRoute = Router();
inspectionRoute.get(
"/lot",
asyncWrapper(async (req, res) => await controller.getInspectionLot(req, res))
);
import { inject, injectable } from "inversify";
import { TYPES } from "~/di/types";
import { CatalogRepository } from "~/s4/service/catalogRepository";
import { InspectionRepository } from "~/s4/service/inspectionRepository";
import { InspectionService } from "~/s4/service/inspectionService";
@injectable()
export class InspectionServiceImpl implements InspectionService {
private inspectionRepo: InspectionRepository;
private catalogRepo: CatalogRepository;
constructor(
@inject(TYPES.InspectionRepository)
inspectionRepo: InspectionRepository,
@inject(TYPES.CatalogRepository)
catalogRepo: CatalogRepository
) {
this.inspectionRepo = inspectionRepo;
this.catalogRepo = catalogRepo;
}
}
const TYPES = {
//Controller
InspectionController: Symbol.for("InspectionController"),
UserController: Symbol.for("UserController"),
//Service
InspectionService: Symbol.for("InspectionService"),
UserService: Symbol.for("UserService"),
//Repository
InspectionRepository: Symbol.for("InspectionRepository"),
CatalogRepository: Symbol.for("CatalogRepository"),
UserRepository: Symbol.for("UserRepository"),
};
export { TYPES };
import { Container } from "inversify";
import { TYPES } from "~/di/types";
import { UserController } from "~/externalPlatform1/controller/userController";
import { UserControllerImpl } from "~/externalPlatform1/controller/userControllerImpl";
import { UserRepositoryImpl } from "~/externalPlatform1/repository/userRepositoryImpl";
import { UserRepository } from "~/externalPlatform1/service/userRepository";
import { UserService } from "~/externalPlatform1/service/userService";
import { UserServiceImpl } from "~/externalPlatform1/service/userServiceImpl";
import { InspectionController } from "~/s4/controller/inspectionController";
import { InspectionControllerImpl } from "~/s4/controller/inspectionControllerImpl";
import { CatalogRepositoryImpl } from "~/s4/repository/catalogRepositoryImpl";
import { InspectionRepositoryImpl } from "~/s4/repository/inspectionRepositoryImpl";
import { CatalogRepository } from "~/s4/service/catalogRepository";
import { InspectionRepository } from "~/s4/service/inspectionRepository";
import { InspectionService } from "~/s4/service/inspectionService";
import { InspectionServiceImpl } from "~/s4/service/inspectionServiceImpl";
const myContainer = new Container();
//Controller
myContainer
.bind<InspectionController>(TYPES.InspectionController)
.to(InspectionControllerImpl);
myContainer.bind<UserController>(TYPES.UserController).to(UserControllerImpl);
//Service
myContainer
.bind<InspectionService>(TYPES.InspectionService)
.to(InspectionServiceImpl);
myContainer.bind<UserService>(TYPES.UserService).to(UserServiceImpl);
//Repository
myContainer
.bind<InspectionRepository>(TYPES.InspectionRepository)
.to(InspectionRepositoryImpl);
myContainer
.bind<CatalogRepository>(TYPES.CatalogRepository)
.to(CatalogRepositoryImpl);
myContainer.bind<UserRepository>(TYPES.UserRepository).to(UserRepositoryImpl);
export { myContainer };
Dependency injection and dependency inversion principle
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
4 | |
4 | |
4 | |
4 | |
3 | |
3 | |
3 | |
3 | |
3 | |
3 |