Skip to content

LiprikON2/Effective-Mobile-Technical-Assessment

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Technical Assessment from Effective Mobile - Store stocks

Running

1. Set up environment variables

cp .env.example .env

2. Run docker-compose

Development mode (w/ hot reloading)

All microservices

docker-compose --profile dev up --build --watch

Single microservice in development mode

docker-compose up stock-dev --build --watch
docker-compose up user-dev --build --watch

Production mode

docker-compose --profile prod up --build

Running migrations (must be run at least once for production)

docker compose run --rm stock npm run migrate
docker compose run --rm stock-history npm run migrate
docker compose run --rm user npm run migrate

3. Interact with containers

Задание 1

Нужно реализовать 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
  • ordered_quantity - количество товара в заказе
    • Constraint: ordered_quantity >= 0
  • total_quantity - общее количество товара
    • Constraint: shelf_quantity + ordered_quantity <= total_quantity
  • created_at - дата создания остатка
Таблица товаров

products

  • id - первичный ключ
  • plu - артикул товара
  • name - название товара
Таблица магазинов

shops

  • id - первичный ключ
  • name - название магазина

Endpoints

Должны быть следующие endpoint'ы:

Cервис истории действий с товарами

Note

Microservice stock-history

  • Language: TypeScript
  • Framework: Feathers.js
  • HTTP platform: Koa.js
  • SQL Query Builder: Knex.js
  • DBMS: PostgreSQL

В сервис "истории действий с товарами" нужно отправлять все события, которые происходят с товарами или остатками. Общение сервисов может происходить любым способом.

Tаблицы

Таблица истории действий с товарами

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 - общее количество товара

Endpoints

Сервис "истории действий с товарами или остатками" должен иметь endpoint, который отдаст историю действий с фильтрами по:

и постраничной навигацией. Фреймворк так же может быть любой, но не nest. Один из сервисов должен быть на JS, для второго можно использовать TS. СУБД - postgresql

Structure

Development

Install dependencies locally (for IntelliSense)

(cd stock && npm i) & (cd stock-history && npm i)

Adding a service (generating CRUD table boilerplate)

(cd ./stock && npx feathers generate service)

Making migrations

  1. Generate empty migration
(cd ./stock && npm run migrate:make -- migration_name)
  1. Manually fill up up and down functions (Example)

Adding a microservice

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 config

Modify package.json:

"migrate": "ts-node init-database && knex migrate:latest"

Задание 2

Сервис пользователей

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% из которых стоит флаг имения проблемы

Endpoints

Нужно сделать endpoint, который проставить флаг проблемы у пользователей в false и посчитает, сколько пользователей имело true в этом флаге. Этот сервис нужно реализовать на nestjs

users.service.ts

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

Development

Install dependencies locally (for IntelliSense)

(cd user && npm i)

Adding a service (generating CRUD table boilerplate)

(cd user && nest g resource my-service-name)

Making migrations

  1. Run user container in prod mode
docker-compose up user --build
  1. Generate migrations against db container
docker-compose build user && docker compose run --rm user npm run migrate:generate --name=migration_name

Seeding database

Generating and seeding (slow)

docker-compose build user-dev && docker compose run --rm user-dev npm run seed

or

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.sql

Seeding 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"

About

BACKEND - Technical assessment task for Effective Mobile

Resources

Stars

Watchers

Forks

Contributors