- Effective Mobile - Technical Assessment
cp .env.example .envAll microservices
docker-compose --profile dev up --build --watchSingle microservice in development mode
docker-compose up stock-dev --build --watchdocker-compose up user-dev --build --watchdocker-compose --profile prod up --buildRunning migrations (must be run at least once for production)
docker compose run --rm stock npm run migratedocker compose run --rm stock-history npm run migratedocker compose run --rm user npm run migrate- Stock microservice swagger docs
- Stock history microservice swagger docs
- User microservice
- PostgreSQL database
psql postgres://user:password@localhost:15432/postgres- Database
stock- Table
stocks
- Table
- Database
stock-history- Table
stocks-history
- Table
- Database
user- Table
users
- Table
- RabbitMQ
Нужно реализовать 2 сервиса
Note
Microservice stock
- Language: JavaScript
- Framework: Feathers.js
- HTTP platform: Koa.js
- SQL Query Builder: Knex.js
- DBMS: PostgreSQL
У товара могут быть следующие поля:
- PLU - артикул товара
- Название товара
- Количество товара на полке
- Количество товара в заказе
- Для какого магазина данный остаток
Warning
Данные денормализованы, их нужно привести к 2-3 нормальной форме
stocks
id- первичный ключproduct_id- товар остаткаshop_id- магазин остаткаshelf_quantity- количество остатка на полках- Constraint:
shelf_quantity >= 0
- Constraint:
ordered_quantity- количество товара в заказе- Constraint:
ordered_quantity >= 0
- Constraint:
total_quantity- общее количество товара- Constraint:
shelf_quantity + ordered_quantity <= total_quantity
- Constraint:
created_at- дата создания остатка
products
id- первичный ключplu- артикул товараname- название товара
shops
id- первичный ключname- название магазина
Должны быть следующие endpoint'ы:
- Создание товара
- Создание остатка
- Увеличение остатка
- Уменьшение остатка
- Получение остатков по фильтрам
plushop_id- количество остатков на полке (с-по)
- количество остатков в заказе (с-по)
- Получение товаров
Note
Microservice stock-history
- Language: TypeScript
- Framework: Feathers.js
- HTTP platform: Koa.js
- SQL Query Builder: Knex.js
- DBMS: PostgreSQL
В сервис "истории действий с товарами" нужно отправлять все события, которые происходят с товарами или остатками. Общение сервисов может происходить любым способом.
products-history
id- первичный ключaction- действие (created,patched,updated,deleted)timestamp- дата внесения измененияresult_id- первичный ключ товараname- название товараplu- артикул товара
stocks-history
id- первичный ключaction- действие (created,patched,updated,deleted)timestamp- дата внесения измененияresult_id- первичный ключ остаткаproduct_id- товар остаткаshop_id- магазин остаткаcreated_at- дата создания остаткаshelf_quantity- количество остатка на полкахordered_quantity- количество товара в заказеtotal_quantity- общее количество товара
Сервис "истории действий с товарами или остатками" должен иметь endpoint, который отдаст историю действий с фильтрами по:
shop_idpludate(с-по)action
и постраничной навигацией. Фреймворк так же может быть любой, но не nest. Один из сервисов должен быть на JS, для второго можно использовать TS. СУБД - postgresql
Install dependencies locally (for IntelliSense)
(cd stock && npm i) & (cd stock-history && npm i)(cd ./stock && npx feathers generate service)- Generate empty migration
(cd ./stock && npm run migrate:make -- migration_name)- Manually fill up
upanddownfunctions (Example)
npm create feathers@latest service-name
(cd service-name && npm install feathers-swagger swagger-ui-dist koa-mount koa-static)
(cd service-name && rm .gitignore .prettierrc)
(cd service-name && npx prettier --write .)
Modify app.ts:
import swagger from 'feathers-swagger'
// <...>
app.configure(
swagger({
docsPath: '/docs',
specs: {
info: {
title: 'Microservice',
description: 'Description',
version: '1.0.0'
},
schemes: ['http', 'https']
},
ui: swagger.swaggerUI({})
})
)Add Dockerfile:
FROM node:lts-alpine
WORKDIR /usr/src/app
COPY package*.json ./
# Fix for npm install taking 10 minutes
# ref: https://forums.docker.com/t/npm-install-in-docker-tutorial-is-taking-forever/139328/13
RUN npm config set strict-ssl false
RUN npm install
COPY . .
EXPOSE 3030
CMD ["npm", "run", "start"]
Add init-database.ts:
import knex from 'knex'
import config from './knexfile'
async function createDatabase() {
if (!config || typeof config.connection === 'string') return
const { database } = config.connection
// Establish connection using default postgres database
config.connection.database = 'postgres'
const db = knex(config)
try {
console.log(`CREATE DATABASE ${database}`)
await db.raw(`CREATE DATABASE ${database}`)
} catch (err) {
// Ignore database already exists error
// @ts-ignore
if (!err.code === '42P04') throw err
}
await db.destroy()
}
createDatabase()Modify knexfile.ts:
export default configModify package.json:
"migrate": "ts-node init-database && knex migrate:latest"Note
Microservice user
- Language: TypeScript
- Framework: Nest.js
- HTTP platform: Express.js
- ORM: TypeORM
- DBMS: PostgreSQL
Нужно написать сервис, который работает с пользователями.
В бд может быть более 1 миллиона пользователей (набить данными бд нужно самостоятельно. Например, написать миграцию, которая это сделает). Каждый пользователь имеет поля:
- Имя
- Фамилия
- Возраст
- Пол
- проблемы: boolean // есть ли проблемы у пользователя
user.entity.ts
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
first_name: string
@Column()
last_name: string
@Column()
has_issues: boolean
@Column({
type: 'enum',
enum: ['male', 'female']
})
gender: string
@Column({ type: 'date' })
birth_date: Date
@Expose() // Makes the virtual properties visible in responses
@Transform(({ obj }) => obj.getAge())
age: number
getAge(): number {
return Math.floor(
(new Date().getTime() - new Date(this.birth_date).getTime()) / (365.25 * 24 * 60 * 60 * 1000)
)
}
}user.factory.ts
export default setSeederFactory(User, (faker) => {
const user: Partial<User> = {
first_name: faker.person.firstName(),
last_name: faker.person.lastName(),
has_issues: faker.datatype.boolean({ probability: 0.3 }),
gender: faker.helpers.arrayElement(['male', 'female']),
birth_date: faker.date.between({ from: '1950-01-01', to: '2005-12-31' })
}
return user
})user.seeder.ts
export default class UserSeeder implements Seeder {
public async run(dataSource: DataSource, factoryManager: SeederFactoryManager): Promise<void> {
const userFactory = factoryManager.get(User)
faker.seed(42)
const iterations = 10000
const batchSize = 100
const progressBar = new ProgressBar(iterations)
console.log(`Generating ${iterations * batchSize} user entries...`)
for (let i = 0; i < iterations; i++) {
await progressBar.update(i)
await userFactory.saveMany(batchSize)
}
progressBar.finish()
console.log(`Finished seeding ${iterations * batchSize} users`)
}
}Генерация 1 000 000 пользователей, у 30% из которых стоит флаг имения проблемы
Нужно сделать endpoint, который проставить флаг проблемы у пользователей в false и посчитает, сколько пользователей имело true в этом флаге. Этот сервис нужно реализовать на nestjs
async resetAndCountIssues() {
const startTime = performance.now()
const queryBuilder = this.usersRepository.createQueryBuilder('users')
const result = await queryBuilder
.update(User)
.set({ has_issues: false })
.where('has_issues = :value', { value: true })
.execute()
const executionTime = performance.now() - startTime
return {
updatedCount: result.affected,
executionTimeMs: Math.round(executionTime * 100) / 100,
message: `Successfully reset ${result.affected} users`
}
}PUT localhost:3000/users/reset-issues
Install dependencies locally (for IntelliSense)
(cd user && npm i)(cd user && nest g resource my-service-name)- Run
usercontainer in prod mode
docker-compose up user --build- Generate migrations against db container
docker-compose build user && docker compose run --rm user npm run migrate:generate --name=migration_nameGenerating and seeding (slow)
docker-compose build user-dev && docker compose run --rm user-dev npm run seedor
Creating data dump of user.users table (fast)
docker exec -i store-db-1 bash -c "PGPASSWORD=password pg_dump -U user -n public -a -t users user" > ./user/src/database/dumps/user_users_dump.sqlSeeding from data dump (fast)
cat ./user/src/database/dumps/user_users_dump.sql | docker exec -i store-db-1 bash -c "PGPASSWORD=password psql -U user -d user"



