Skip to content
This repository was archived by the owner on Dec 5, 2021. It is now read-only.

Commit 5203be6

Browse files
committed
Add the course materials
1 parent 2df8d45 commit 5203be6

File tree

666 files changed

+40699
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

666 files changed

+40699
-0
lines changed

omp-docs/images/grpc.png

218 KB
Loading

omp-docs/images/kafka.png

231 KB
Loading

omp-docs/images/observability.png

231 KB
Loading

omp-docs/images/postgres.png

213 KB
Loading

omp-docs/images/retranslator.png

223 KB
Loading

omp-docs/images/schema.png

466 KB
Loading

omp-docs/task-1.md

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Ozon Marketplace Project
2+
3+
![schema](images/schema.png)
4+
5+
Дальше везде используются **placeholder**-ы:
6+
- `{domain}`,`{Domain}`
7+
- `{subdomain}`,`{Subdomain}`
8+
9+
Например, для поддомена `package` из домена `logistic` значение **placeholder**-ов будет:
10+
- `{domain}`,`{Domain}` = `logistic`,`Logistic`
11+
- `{subdomain}`,`{Subdomain}` = `package`,`Package`
12+
- `{domain}`/`{subdomain}` = `logistic`/`package`
13+
---
14+
15+
16+
### Задание 1
17+
18+
1. Сделать форк **ozonmp/omp-bot** репозитория в свой профиль
19+
2. Запросить у своего тьютора свой домен/поддомен: **{domain}/{subdomain}**
20+
3. Добавить в ветку `feature/task-1` своего форка поддержку следующих команд:
21+
```
22+
/help__{domain}__{subdomain} — print list of commands
23+
/get__{domain}__{subdomain} — get a entity
24+
/list__{domain}__{subdomain} — get a list of your entity (💎: with pagination via telegram keyboard)
25+
/delete__{domain}__{subdomain} — delete an existing entity
26+
27+
/new__{domain}__{subdomain} — create a new entity // not implemented (💎: implement list fields via arguments)
28+
/edit__{domain}__{subdomain} — edit a entity // not implemented
29+
```
30+
4. Сделать PR из ветки `feature/task-1` своего форка в ветку `master` своего форка
31+
5. Отправить ссылку на PR личным сообщением своему тьютору до конда дедлайна сдачи (см. таблицу прогресса)
32+
33+
#### Рецепт
34+
35+
Для добавления поддержки команд в рамках своего поддомена:
36+
37+
1. Написать структуру `{Subdomain}` с методом `String()`
38+
2. Написать интерфейс `{Subdomain}Service` и **dummy** имплементацию
39+
3. Написать интерфейс `{Subdomain}Commander` по обработке команд
40+
41+
---
42+
43+
2. Реализовать `{Subdomain}Service` в **internal/service/{domain}/{subdomain}/**
44+
45+
```go
46+
package {subdomain}
47+
48+
import "github.com/ozonmp/omp-bot/internal/model/{domain}"
49+
50+
type {Subdomain}Service interface {
51+
Describe({subdomain}ID uint64) (*{domain}.{Subdomain}, error)
52+
List(cursor uint64, limit uint64) ([]{domain}.{Subdomain}, error)
53+
Create({domain}.{Subdomain}) (uint64, error)
54+
Update({subdomain}ID uint64, {subdomain} {domain}.{Subdomain}) error
55+
Remove({subdomain}ID uint64) (bool, error)
56+
}
57+
58+
type Dummy{Subdomain}Service struct {}
59+
60+
func NewDummy{Subdomain}Service() *Dummy{Subdomain}Service {
61+
return &Dummy{Subdomain}Service{}
62+
}
63+
64+
// ...
65+
```
66+
67+
---
68+
69+
3. Реализовать `{Subdomain}Commander` по обработке команд в **internal/app/commands/{domain}/{subdomain}/**
70+
71+
```go
72+
package {subdomain}
73+
74+
import (
75+
model "github.com/ozonmp/omp-bot/internal/model/{domain}"
76+
service "github.com/ozonmp/omp-bot/internal/service/{domain}/{subdomain}"
77+
)
78+
79+
type {Subdomain}Commander interface {
80+
Help(inputMsg *tgbotapi.Message)
81+
Get(inputMsg *tgbotapi.Message)
82+
List(inputMsg *tgbotapi.Message)
83+
Delete(inputMsg *tgbotapi.Message)
84+
85+
New(inputMsg *tgbotapi.Message) // return error not implemented
86+
Edit(inputMsg *tgbotapi.Message) // return error not implemented
87+
}
88+
89+
func New{Subdomain}Commander(bot *tgbotapi.BotAPI, service service.{Subdomain}Service) {Subdomain}Commander {
90+
// ...
91+
}
92+
```

omp-docs/task-2.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Ozon Marketplace Project
2+
3+
![schema](images/retranslator.png)
4+
5+
Дальше везде используются **placeholder**-ы:
6+
- `{domain}`,`{Domain}`
7+
- `{subdomain}`,`{Subdomain}`
8+
9+
Например, для поддомена `package` из домена `logistic` значение **placeholder**-ов будет:
10+
- `{domain}`,`{Domain}` = `logistic`,`Logistic`
11+
- `{subdomain}`,`{Subdomain}` = `package`,`Package`
12+
- `{domain}`/`{subdomain}` = `logistic`/`package`
13+
---
14+
15+
### Задание 2
16+
17+
1. Создать репозиторий в формате `{domain-kw}-{subdomain}-api`
18+
19+
2. Описать сущность `{domain}.{Subdomain}` и `{domain}.{Subdomain}Event` в **internal/model/{subdomain}.go**
20+
21+
3. Реализовать паттерн consumer-producer из **db** в **kafka** на основе интерфейсов [EventRepo](https://github.com/ozonmp/omp-demo-api/blob/b847b3ae4a3c9e1d25e31e077c847a22f8b7aa99/internal/app/repo/event.go#L7) и [EventSender](https://github.com/ozonmp/omp-demo-api/blob/b847b3ae4a3c9e1d25e31e077c847a22f8b7aa99/internal/app/sender/event.go#L7) для одного типа события **Created**
22+
23+
4. Написать тесты
24+
25+
5. Синхронизацию работы потоков сделать через `context` 💎
26+
27+
6. Создавать задачи у **workerpool** по обработке батчевых идентификаторов записей событий 💎
28+
29+
7. Поддержать несколько типов событий учитывая корректный порядок 💎
30+
31+
8. Реализовать гарантию доставки **At-least-once** 💎
32+
33+
9. Найти скрытые ошибки в коде 💎
34+
35+
**Рецепт**
36+
37+
[omp-demo-api](https://github.com/ozonmp/omp-demo-api)
38+
39+
P.S. Обратите внимание используется зеркальная (внешняя) точка зрения на вопрос, кто является потребителем, а кто является производителем.
40+
Поэтому паттерн назвали **consumer-producer** и классы переименовали.

omp-docs/task-3.md

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Ozon Marketplace Project
2+
3+
![schema](images/grpc.png)
4+
5+
Дальше везде используются **placeholder**-ы:
6+
7+
- `{domain}`,`{Domain}`
8+
- `{subdomain}`,`{Subdomain}`
9+
10+
Например, для поддомена `package` из домена `logistic` значение **placeholder**-ов будет:
11+
12+
- `{domain}`,`{Domain}` = `logistic`,`Logistic`
13+
- `{subdomain}`,`{Subdomain}` = `package`,`Package`
14+
- `{domain}`/`{subdomain}` = `logistic`/`package`
15+
- `{subdomains}`,`{Subdomains}` = `packages`,`Packages`
16+
17+
---
18+
19+
### Задание 3
20+
21+
1. Сделать **rebase** своего репозитория `{kw-domain}-{subdomain}-api` на [omp-template-api](https://github.com/ozonmp/omp-template-api)
22+
2. Добавить в **proto** следующие **handler**-ы (пример [template](https://github.com/ozonmp/omp-template-api/blob/be1223fb1d1c9751b0d9db1d6e2dfff6ba4c9316/protos/ozonmp/omp_template_api/v1/omp_template_api.proto)):
23+
1. `Create{Subdomain}`
24+
2. `Describe{Subdomain}`
25+
3. `List{Subdomains}`
26+
4. `Remove{Subdomain}`
27+
3. Добавить теги валидации в поля сообщений (пример [template](https://github.com/ozonmp/omp-template-api/blob/be1223fb1d1c9751b0d9db1d6e2dfff6ba4c9316/protos/ozonmp/omp_template_api/v1/omp_template_api.proto#L28))
28+
4. Сделать рефакторинг: заменить `template` на `{subomain}` (см. рецепт)
29+
5. Сгенерировать **gRPC** код клиента и сервера (make generate)
30+
6. Имплементировать код новых ручек в **internal/api/api.go** (пример [template](https://github.com/ozonmp/omp-template-api/blob/be1223fb1d1c9751b0d9db1d6e2dfff6ba4c9316/internal/api/api.go#L34))
31+
1. Код ручек должен просто логгировать вызовы (с уровнем `debug`)
32+
2. Возвращать пустой ответ или внутреннюю ошибку (`not implemented`)
33+
3. При желание разделить по разным файлам имплементацию ручек
34+
7. Протестировать через **grpc_cli** (или **grpcurl**) написанные ручки (пример [template](https://github.com/ozonmp/omp-template-api/blob/main/DOCS.md#grpc))
35+
8. Написать тесты по обработке не валидных запросов :gem:
36+
9. Настроить маршрутизацию при запуске контейнеров: :gem: (можно сделать через [dist](https://github.com/ozonmp/omp-grpc-template/tree/master/swagger/dist) директорию)
37+
- с `0.0.0.0:8080/swagger` на контейнер **swagger**
38+
- c `0.0.0.0:8080/api` на контейнер сервиса на порт **gateway**-a
39+
10. Сгенерировать **Python** код клиента и задеплоить его в **PyPi** :gem: (пример [template](https://github.com/ozonmp/omp-template-api/blob/main/DOCS.md#python-client))
40+
41+
42+
**Рецепт**
43+
44+
Переезд проекта на рельсы шаблона
45+
```sh
46+
export domain_kw=omp
47+
export subdomain=demo
48+
49+
git remote add template https://github.com/ozonmp/omp-template-api
50+
git fetch template main
51+
git rebase template/main
52+
git checkout template/main -- Makefile go.mod go.sum
53+
git rebase --continue
54+
rm -rf pkg/omp-template-api
55+
mkdir pkg/${domain_kw}-${subdomain}-api
56+
mv api/ozonmp/omp_template_api/v1/omp_template_api.proto \
57+
api/ozonmp/omp_template_api/v1/${domain_kw}_${subdomain}_api.proto
58+
mv api/ozonmp/omp_template_api api/ozonmp/${domain_kw}_${subdomain}_api
59+
mv pypkg/omp-template-api pypkg/${domain_kw}-${subdomain}-api
60+
// grep (exclude 'api/google' dir)
61+
// - template -> ${subdomain}
62+
// - grep omp -> ${domain_kw}
63+
make generate
64+
go mod tidy
65+
make build
66+
# перенесли в шаблонном репозитории README.md в DOCS.md, чтобы было меньше коонфликтов при rebase
67+
mv DOCS.md README.md
68+
git add .
69+
git commit -m"refactored"
70+
```
71+
72+
Описание сообщений
73+
```proto
74+
// ...
75+
76+
message {Subdomain} {
77+
uint64 id = 1;
78+
string foo = 2;
79+
}
80+
81+
message Create{Subdomain}V1Request {
82+
string foo = 1;
83+
}
84+
85+
message Create{Subdomain}V1Response {
86+
uint64 {subdomain}_id = 1;
87+
}
88+
89+
message Describe{Subdomain}V1Request {
90+
uint64 {subdomain}_id = 1;
91+
}
92+
93+
message List{Subdomains}V1Request {
94+
}
95+
96+
message List{Subdomains}V1Response {
97+
repeated {Subdomain} items = 1;
98+
}
99+
100+
message Remove{Subdomain}V1Request {
101+
uint64 {subdomain}_id = 1;
102+
}
103+
104+
message Remove{Subdomain}V1Response {
105+
bool found = 1;
106+
}
107+
```

omp-docs/task-4.md

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Ozon Marketplace Project
2+
3+
![schema](images/postgres.png)
4+
5+
Дальше везде используются **placeholder**-ы:
6+
7+
- `{domain}`,`{Domain}`
8+
- `{subdomain}`,`{Subdomain}`
9+
10+
Например, для поддомена `package` из домена `logistic` значение **placeholder**-ов будет:
11+
12+
- `{domain}`,`{Domain}` = `logistic`,`Logistic`
13+
- `{subdomain}`,`{Subdomain}` = `package`,`Package`
14+
- `{domain}`/`{subdomain}` = `logistic`/`package`
15+
- `{subdomains}`,`{Subdomains}` = `packages`,`Packages`
16+
17+
---
18+
19+
**Задание IV**
20+
21+
1. Реализовать методы для интерфейса `Repo`
22+
2. Написать миграции для создания таблиц и создания индексов
23+
3. Реализовать методы для интерфейса `RepoEvent` (сообщения в **proto**)
24+
4. Подготовить **dataset** для таблиц `subdomains` и `subdomains_events` :gem:
25+
5. Реализовать поддержку вариаций типов событий на обновление сущности `subdomain` :gem:
26+
6. Обеспечить защиту от **sql**-инъекции :gem:
27+
7. Настроить партиципирование таблицы на **N** частей :gem:
28+
8. Написать тесты :gem:
29+
30+
---
31+
32+
**Рецепт**
33+
34+
Используя паттерн [Transactional Outbox Pattern](https://microservices.io/patterns/data/transactional-outbox.html)
35+
36+
1. Создать таблицы следующих форматов:
37+
38+
`{subdomains}` таблица
39+
40+
| id **bigint** | ... | removed **bool** | created **timestamp** | updated **timestamp** |
41+
| :-----------: | :--: | :--------------: | :-------------------: | :-------------------: |
42+
| | | | | |
43+
44+
45+
46+
`{subdomains}_events` 📤 таблица
47+
48+
| id **bigint** | {subdomain}_id **bigint** | type **text** | status | payload **jsonb** | updated **timestamp** |
49+
| :-----------: | :-----------------------: | :-----------: | ------ | :----------------: | ------- |
50+
| | | Created | lock | `SubdomainCreated` | |
51+
| | | Updated | lock | `SubdomainUpdated` | |
52+
| | | Removed | | `SubdomainRemoved` | |
53+
54+
55+
2. Составить список sql запросов для таблицы `{subdomains}`, потом для `{subdomains}_events`
56+
57+
```sql
58+
-- Lock n events 🐘 🏆
59+
```
60+
61+
3. Имплементировать методы интерфейсов с помощью [squirell](https://github.com/Masterminds/squirrel)
62+
63+
```go
64+
type Repo interface {
65+
Add(*model.Subdomain) (uint64, error)
66+
Get(subdomainID uint64) (*model.Subdomain, error)
67+
List(limit uint64, cursor uint64) ([]model.Subdomain, error)
68+
Remove(subdomainID uint64) (bool, error)
69+
}
70+
```
71+
72+
73+
```go
74+
type EventRepo interface {
75+
Lock(n uint64) ([]model.SubdomainEvent, error)
76+
Unlock(eventIDs []uint64) error
77+
Remove(eventIDs []uint64) (bool, error)
78+
}
79+
```
80+
81+
4. Написать и накатить миграции
82+
```sh
83+
$ cd migrations
84+
$ cat .env
85+
PGPASSWORD=docker
86+
PGUSER=docker
87+
$ set -o allexport; source .env; set +o allexport
88+
$ goose postgres "host=localhost sslmode=disable dbname={domain-kw}_{subdomain}_api port=5432" up
89+
$ goose postgres "host=localhost sslmode=disable dbname={domain-kw}_{subdomain}_api port=5432" status
90+
```
91+
5. Поднять сервис и пострелять в него разными запросами [пример](https://github.com/ozonmp/omp-template-api/blob/main/DOCS.md#gateway)

omp-docs/task-5.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Ozon Marketplace Project
2+
3+
![schema](images/observability.png)
4+
5+
Дальше везде используются **placeholder**-ы:
6+
7+
- `{domain}`,`{Domain}`
8+
- `{subdomain}`,`{Subdomain}`
9+
10+
Например, для поддомена `package` из домена `logistic` значение **placeholder**-ов будет:
11+
12+
- `{domain}`,`{Domain}` = `logistic`,`Logistic`
13+
- `{subdomain}`,`{Subdomain}` = `package`,`Package`
14+
- `{domain}`/`{subdomain}` = `logistic`/`package`
15+
- `{subdomains}`,`{Subdomains}` = `packages`,`Packages`
16+
17+
---
18+
19+
**Задание V**
20+
21+
_Логирование_
22+
23+
1. Покрыть логированием код обработки ручек со всем уровнем вложенности
24+
2. Добавить поддержку изменения уровня логирования через заголовок запроса
25+
3. Добавить поддержку детализированного вывода запроса и ответа через **middleware** :gem:
26+
4. Добавить отображение в **swagger** отладочных заголовков :gem:
27+
28+
_Трассировка_
29+
30+
1. Добавить создание спанов для ручек
31+
2. Добавить заполнение необходимых полей и ошибок
32+
3. Пострелять с помощью **hey**
33+
34+
_Метрики_
35+
36+
1. Добавить метрики на
37+
1. Кол-во **NotFound** событий 🔍
38+
2. Кол-во создаваемых **CUD** событий 🔍
39+
3. Кол-во обрабатываемых событий в ретрансляторе 🔍
40+
2. Добавить **dashboard** на отображение метрик :gem:

0 commit comments

Comments
 (0)