Micro-services architecture relies on the Back-end For Front-end -BFF- design pattern ☛
“index” as primary key support
import * as Mongoose from "mongoose"; export default interface CriminalCase extends Mongoose.Document { criminal_case_number: string; jurisdiction_name: string; date_of_criminal_case: Date; } // create table CRIMINAL_CASE( // CRIMINAL_CASE_NUMBER varchar(10), // JURISDICTION_NAME varchar(30), // DATE_OF_CRIMINAL_CASE Date, // constraint CRIMINAL_CASE_key primary key(CRIMINAL_CASE_NUMBER,JURISDICTION_NAME)); const schema = new Mongoose.Schema( { criminal_case_number: { type: Mongoose.Schema.Types.String, required: true, maxlength: 10 }, jurisdiction_name: { type: Mongoose.Schema.Types.String, required: true, maxlength: 30 }, date_of_criminal_case: { type: Mongoose.Schema.Types.Date, required: true, default: Date.now } } ); // Primary key (performance issues): schema.index({'criminal_case_number': 1 /* ascending '1', descending '-1' */, 'jurisdiction_name': 1}, {unique: true});
“index” as foreign key support
import * as Mongoose from "mongoose"; import Prisoner from "./Prisoner"; export default interface JudicialDecision extends Mongoose.Document { decision_type_number: string; prisoner: Prisoner; date_of_decision: Date; } // create table JUDICIAL_DECISION( // DECISION_TYPE_NUMBER varchar(1), // PRISON_FILE_NUMBER varchar(10), // DATE_OF_DECISION Date, // constraint JUDICIAL_DECISION_key primary key(DECISION_TYPE_NUMBER,PRISON_FILE_NUMBER,DATE_OF_DECISION), // constraint JUDICIAL_DECISION_fk foreign key(PRISON_FILE_NUMBER) references PRISONER(PRISON_FILE_NUMBER)); const schema = new Mongoose.Schema( { decision_type_number: { type: Mongoose.Schema.Types.String, required: true, enum: ['1', '2', '3'], maxlength: 1 }, prisoner: { type: Mongoose.Schema.Types.ObjectId, required: true, ref: 'PRISONER' }, date_of_decision: { type: Mongoose.Schema.Types.Date, required: true, default: Date.now } }, // 'discriminatorKey' option tells mongoose to add a path to the schema called 'decision_type_number' so that it tracks which type of document this is: {discriminatorKey: 'decision_type_number'} ); // Primary key (performance issues!): schema.index({decision_type_number: 1, prisoner: 1, date_of_decision: 1}, {unique: true});
Create, Read, Update, Delete -CRUD- services
import CriminalCase, {CriminalCaseModel} from '../schemes/CriminalCase'; export const Get_collection_name = () => CriminalCaseModel.collection.name; export default class CriminalCaseDAO { public static Create(criminal_case: CriminalCase): Promise<CriminalCase> { return CriminalCaseModel.create(criminal_case); } public static Delete(criminal_case: CriminalCase): Promise<any>; public static Delete(criminal_case_number: string, jurisdiction_name: string): Promise<any>; public static Delete(x: CriminalCase | string, jurisdiction_name?: string) { return typeof x === "string" ? CriminalCaseModel.deleteOne({criminal_case_number: x, jurisdiction_name: jurisdiction_name}).exec() : CriminalCaseModel.deleteOne({ criminal_case_number: x.criminal_case_number, jurisdiction_name: jurisdiction_name }).exec(); } public static GetCriminalCase(criminal_case_number: string, jurisdiction_name: string, id = false): Promise<CriminalCase | null> { return CriminalCaseModel.findOne({ criminal_case_number: criminal_case_number, jurisdiction_name: jurisdiction_name }) .select({_id: id, __v: false}) .lean() .exec(); } public static GetCriminalCases(): Promise<CriminalCase[]> { return CriminalCaseModel.find() .select({_id: false, __v: false}) .lean() .exec(); } public static Update(criminal_case: CriminalCase): Promise<CriminalCase | any> { return CriminalCaseModel.updateOne({ criminal_case_number: criminal_case.criminal_case_number, jurisdiction_name: criminal_case.jurisdiction_name }, {$set: {...criminal_case}}) .select({_id: false, __v: false}) .exec(); } }
import express from "express"; import Joi from 'joi'; import CriminalCase from "../../persistence/schemes/CriminalCase"; import CriminalCaseDAO, {Get_collection_name} from '../../persistence/DAOs/CriminalCaseDAO'; import Validator, {ValidationSource} from "./Validator"; export {Get_collection_name}; const criminal_case_id = Joi.object().keys({ criminal_case_number: Joi.string().required().max(10), jurisdiction_name: Joi.string().required().max(30) }); const criminal_case_Joi_schema = Joi.object().keys({ criminal_case_number: Joi.string().required().max(10), jurisdiction_name: Joi.string().required().max(30), date_of_criminal_case: Joi.date().required() }); // Mini. app. for '/criminal_cases' sub-path: export const router = express.Router(); // http://localhost:1963/NYCP/API/criminal_cases router.get('', (request, response) => { console.log("READ: " + request.baseUrl + request.path); response.redirect(request.baseUrl + request.path + 'all'); }); // http://localhost:1963/NYCP/API/criminal_cases/one?criminal_case_number=M13000&jurisdiction_name=Marseille router.get('/one', Validator(criminal_case_id /*, ValidationSource.QUERY */), (request, response) => { console.log("READ: " + request.baseUrl + request.path + JSON.stringify(request.query)); CriminalCaseDAO.GetCriminalCase(request.query.criminal_case_number as string, request.query.jurisdiction_name as string).then(data => response.status(200).json(data)) .catch(error => response.status(400).json({error, message: error.message})); }); // http://localhost:1963/NYCP/API/criminal_cases/all router.get('/all', (request, response) => { console.log("READ: " + request.baseUrl + request.path); CriminalCaseDAO.GetCriminalCases().then(data => response.status(200).json(data)) .catch(error => response.status(400).json({error, message: error.message})); }); // curl -d "criminal_case_number=P64000&jurisdiction_name=Pau&date_of_criminal_case=2002-03-23" http://localhost:1963/NYCP/API/criminal_cases/ router.post('/', Validator(criminal_case_Joi_schema, ValidationSource.BODY), async (request, response) => { console.log("CREATE: " + request.baseUrl + request.path + JSON.stringify(request.body)); try { const criminal_case = await CriminalCaseDAO.GetCriminalCase(request.body.criminal_case_number, request.body.jurisdiction_name); if (criminal_case) throw new Error('Criminal case ' + JSON.stringify(request.body) + ' already exists...'); CriminalCaseDAO.Create(request.body as CriminalCase).then(data => response.status(201).json(data)) .catch(error => response.status(400).json({error, message: error.message})); } catch (error: unknown) { response.status(400).json({error, message: (error as Error).message}); } }); // curl -X PUT http://localhost:1963/NYCP/API/criminal_cases/P64000\&Pau\&2002-03-23 router.put('/:criminal_case_number&:jurisdiction_name&:date_of_criminal_case', Validator(criminal_case_Joi_schema, ValidationSource.PARAMS), async (request, response) => { console.log("UPDATE: " + request.baseUrl + request.path + JSON.stringify(request.params)); try { const criminal_case = await CriminalCaseDAO.GetCriminalCase(request.params.criminal_case_number, request.params.jurisdiction_name); if (!criminal_case) throw new Error('Criminal case ' + request.params.criminal_case_number + '-' + request.params.jurisdiction_name + ' does not exist...'); criminal_case.date_of_criminal_case = new Date(request.params.date_of_criminal_case); CriminalCaseDAO.Update(criminal_case) .then(data => response.status(200).json(data)) .catch(error => response.status(400).json({error, message: error.message})); } catch (error: unknown) { response.status(400).json({error, message: (error as Error).message}); } }); // curl -X DELETE http://localhost:1963/NYCP/API/criminal_cases/P64000\&Pau router.delete('/:criminal_case_number&:jurisdiction_name', Validator(criminal_case_id, ValidationSource.PARAMS), async (request, response) => { console.log("DELETE: " + request.baseUrl + request.path + JSON.stringify(request.params)); try { const criminal_case = await CriminalCaseDAO.GetCriminalCase(request.params.criminal_case_number, request.params.jurisdiction_name); if (!criminal_case) throw new Error('Criminal case ' + request.params.criminal_case_number + '-' + request.params.jurisdiction_name + ' does not exist...'); CriminalCaseDAO.Delete(criminal_case) .then(data => response.status(200).json(data)) .catch(error => response.status(400).json({error, message: error.message})); } catch (error: unknown) { response.status(400).json({error, message: (error as Error).message}); } });
SQL query
SELECT * FROM Prisoner WHERE prison_file_number NOT IN (SELECT prison_file_number FROM Conviction);
NoSQL query
import express from 'express'; import {Get_collection_name} from '../../persistence/DAOs/JudicialDecisionDAO'; import {PrisonerModel} from "../../persistence/schemes/Prisoner"; // Mini. app. for '/Under_remand' sub-path: export const router = express.Router(); // curl http://localhost:1963/NYCP/API/Under_remand router.get('/', (request, response) => { console.log("Use case: " + request.baseUrl + request.path); PrisonerModel.aggregate([ { $lookup: { from: Get_collection_name(), // Search in 'judicial_decisions' collection... let: {prisoner_id: "$_id"}, // Get '_id' field in 'prisoners' collection... pipeline: [ { $match: { $expr: { // 'prisoner' field in 'judicial_decisions' collection matches with 'prisoner_id' $and: [{$eq: ["$decision_type_number", "1"]}, {$eq: ["$prisoner", "$$prisoner_id"]}] } } } ], as: "convictions" } }, // Only keep those for whom 'convictions' array (added) field is empty, i.e., no conviction yet... {$match: {convictions: {$size: 0}}}, // Only 'prison_file_number' data... {$project: {_id: false, prison_file_number: true}} ]).then(data => response.status(200).json(data)) .catch(error => response.status(400).json({error, message: error.message})); });