Slide 1

Slide 1 text

Пишем свой фреймворк поверх Django Иван Елфимов 2024-07-23

Slide 2

Slide 2 text

Зачем мы пишем свой фреймворк поверх Django Иван Елфимов 2024-07-23

Slide 3

Slide 3 text

Давайте знакомиться ○Иван Елфимов ○инженер-электрохимик ○5 лет руководил разработкой B2B.Ostrovok.ru ○сейчас Developer Advocate в Островке

Slide 4

Slide 4 text

4 Объявляем billing_api = APIView( name='billing', steps=[ steps.consumer_keys_auth, steps.validation, steps.rate_limit, steps.lock, ], consumer_keys=[settings.BILLING_CONSUMER_KEY], ) BillingApiView = billing_api.View billing_api_method = billing_api.method

Slide 5

Slide 5 text

5 Используем class ContractInfoView(BillingApiView): @billing_api_method() def get( self, request: HttpRequest, data_in: ContractInfoIn ) -> ContractInfoOut: # ...

Slide 6

Slide 6 text

6 Не единственный фреймворк в Островке

Slide 7

Slide 7 text

6 Не единственный фреймворк в Островке ●некоторые плотно на DRF

Slide 8

Slide 8 text

6 Не единственный фреймворк в Островке ○некоторые плотно на DRF ●кто-то смотрит на Django Ninja

Slide 9

Slide 9 text

6 Не единственный фреймворк в Островке ○некоторые плотно на DRF ○кто-то смотрит на Django Ninja ●иногда копипастят

Slide 10

Slide 10 text

6 Не единственный фреймворк в Островке ○некоторые плотно на DRF ○кто-то смотрит на Django Ninja ○иногда копипастят ●или пишут своё (привет, extrota и crm)

Slide 11

Slide 11 text

7 С чего начинается

Slide 12

Slide 12 text

7 С чего начинается ●много одинакового кода

Slide 13

Slide 13 text

7 С чего начинается ○много одинакового кода ●8 лет копипастим 200+ строк

Slide 14

Slide 14 text

7 С чего начинается ○много одинакового кода ○8 лет копипастим 200+ строк ●уже 20 копий декоратора

Slide 15

Slide 15 text

7 С чего начинается ○много одинакового кода ○8 лет копипастим 200+ строк ○уже 20 копий декоратора ●повторяем ошибки

Slide 16

Slide 16 text

7 С чего начинается ○много одинакового кода ○8 лет копипастим 200+ строк ○уже 20 копий декоратора ○повторяем ошибки ●меняются библиотеки валидации

Slide 17

Slide 17 text

7 С чего начинается ○много одинакового кода ○8 лет копипастим 200+ строк ○уже 20 копий декоратора ○повторяем ошибки ○меняются библиотеки валидации ●мидлвари в джанге недостаточно гибкие

Slide 18

Slide 18 text

8 Old school декоратор def api_method(methods: Optional[list], model_in=None): def wrapper(view): @csrf_exempt @wraps(view) def wrapped(request, *args, **kwargs): CHECK METHOD STEP GET DATA RAW STEP AUTH STEP VALIDATION STEP VIEW STEP return wrapped return wrapper

Slide 19

Slide 19 text

8 Old school декоратор def api_method(methods: Optional[list], model_in=None): def wrapper(view): @csrf_exempt @wraps(view) def wrapped(request, *args, **kwargs): CHECK METHOD STEP GET DATA RAW STEP AUTH STEP VALIDATION STEP VIEW STEP return wrapped return wrapper

Slide 20

Slide 20 text

8 Old school декоратор CHECK METHOD STEP GET DATA RAW STEP AUTH STEP VALIDATION STEP VIEW STEP def api_method(methods: Optional[list], model_in=None): def wrapper(view): @csrf_exempt @wraps(view) def wrapped(request, *args, **kwargs): return wrapped return wrapper

Slide 21

Slide 21 text

9 Другой пример def intranet_api_method(methods: list, endpoint: str, **kwargs): def wrapper(view): @csrf_exempt @wraps(view) def wrapped(request, *args, **kwargs): CHECK HELP STEP CHECK METHOD STEP AUTH STEP GET DATA RAW STEP VALIDATION STEP VIEW STEP return wrapped return wrapper

Slide 22

Slide 22 text

9 Другой пример def intranet_api_method(methods: list, endpoint: str, **kwargs): def wrapper(view): @csrf_exempt @wraps(view) def wrapped(request, *args, **kwargs): CHECK HELP STEP CHECK METHOD STEP AUTH STEP GET DATA RAW STEP VALIDATION STEP VIEW STEP return wrapped return wrapper

Slide 23

Slide 23 text

9 Другой пример CHECK HELP STEP CHECK METHOD STEP AUTH STEP GET DATA RAW STEP VALIDATION STEP VIEW STEP def intranet_api_method(methods: list, endpoint: str, **kwargs): def wrapper(view): @csrf_exempt @wraps(view) def wrapped(request, *args, **kwargs): return wrapped return wrapper

Slide 24

Slide 24 text

9 Другой пример CHECK HELP STEP def intranet_api_method(methods: list, endpoint: str, **kwargs): def wrapper(view): @csrf_exempt @wraps(view) def wrapped(request, *args, **kwargs): CHECK METHOD STEP AUTH STEP GET DATA RAW STEP VALIDATION STEP VIEW STEP return wrapped return wrapper

Slide 25

Slide 25 text

10 Тем временем в компании

Slide 26

Slide 26 text

10 Тем временем в компании ●стало больше сервисов и проектов

Slide 27

Slide 27 text

10 Тем временем в компании ○стало больше сервисов и проектов ●нужно вкладываться в логи, метрики и алерты

Slide 28

Slide 28 text

10 Тем временем в компании ○стало больше сервисов и проектов ○нужно вкладываться в логи, метрики и алерты ●больше людей → чаще просят доки на апишку

Slide 29

Slide 29 text

11 Тем временем у меня

Slide 30

Slide 30 text

11 Тем временем у меня ●Dry Python

Slide 31

Slide 31 text

11 Тем временем у меня ○Dry Python ●Go

Slide 32

Slide 32 text

11 Тем временем у меня ○Dry Python ○Go ●Haskell

Slide 33

Slide 33 text

11 Тем временем у меня ○Dry Python ○Go ○Haskell ●чистый код

Slide 34

Slide 34 text

12 Фабрика декораторов @property def View(self, *args, **kwargs): @method_decorator(csrf_exempt, name='dispatch') class _View(DjangoView): def http_method_not_allowed(this, request, *args, **kwargs): return self._error_response(errors.ERROR_METHOD) return _View

Slide 35

Slide 35 text

12 Фабрика декораторов class _View(DjangoView): @property def View(self, *args, **kwargs): @method_decorator(csrf_exempt, name='dispatch') def http_method_not_allowed(this, request, *args, **kwargs): return self._error_response(errors.ERROR_METHOD) return _View

Slide 36

Slide 36 text

13 Фабрика декораторов billing_api = APIView( name='billing', steps=[], ) BillingApiView = billing_api.View billing_api_method = billing_api.method

Slide 37

Slide 37 text

13 Фабрика декораторов BillingApiView = billing_api.View billing_api = APIView( name='billing', steps=[], ) billing_api_method = billing_api.method

Slide 38

Slide 38 text

14 Гибрид class-based и обычных view def method(self, *args, **kwargs): def wrapper(_method): @wraps(_method) def wrapped(view_obj, request: HttpRequest, *m_args, **m_kwargs): ctx = dict( # LOTS OF OTHER ARGS request=request, ) return self._view(view_obj, _method, ctx, *m_args, **m_kwargs) return wrapped return wrapper

Slide 39

Slide 39 text

15 Гибрид class-based и обычных view billing_api = APIView( name='billing', steps=[], ) BillingApiView = billing_api.View billing_api_method = billing_api.method

Slide 40

Slide 40 text

15 Гибрид class-based и обычных view billing_api_method = billing_api.method billing_api = APIView( name='billing', steps=[], ) BillingApiView = billing_api.View

Slide 41

Slide 41 text

16 Гибрид class-based и обычных view class ContractInfoView(BillingApiView): @billing_api_method() def get( self, request: HttpRequest, data_in: ContractInfoIn ) -> ContractInfoOut: # ...

Slide 42

Slide 42 text

16 Гибрид class-based и обычных view class ContractInfoView(BillingApiView): @billing_api_method() def get( self, request: HttpRequest, data_in: ContractInfoIn ) -> ContractInfoOut: # ...

Slide 43

Slide 43 text

16 Гибрид class-based и обычных view @billing_api_method() class ContractInfoView(BillingApiView): def get( self, request: HttpRequest, data_in: ContractInfoIn ) -> ContractInfoOut: # ...

Slide 44

Slide 44 text

17 Имплементация stories на минималках Фактически Dry Python Stories aka Returns (нет) def _view(self, view_obj, method, ctx, *args, **kwargs): for step in self.steps: # METRICS ctx = step(method, ctx) if error := ctx.get('error'): return self._error_response(error, ctx.get('debug'))

Slide 45

Slide 45 text

18 Обработчик исключений try: # LOCK MANAGER data_out = method(view_obj, request, data_in, *args, **kwargs) status = 'error' if ctx.get('error') else 'ok' # METRICS except LockError: return self._error_response(errors.ERROR_BUSY) except ErrorResponse as e: return e.http_response except RawResponse as e: return e.http_response except Exception as e: return self._error_response(errors.ERROR_UNKNOWN, debug=str(e)) return HttpResponse(json.dumps(data_out.dict()), content_type=CT_JSON)

Slide 46

Slide 46 text

19 Возвращать или рейзить

Slide 47

Slide 47 text

19 Возвращать или рейзить ●raise для ответов с ошибками

Slide 48

Slide 48 text

19 Возвращать или рейзить ○raise для ответов с ошибками ●raise для ответов с файлами

Slide 49

Slide 49 text

19 Возвращать или рейзить ○raise для ответов с ошибками ○raise для ответов с файлами ●return – для ответов 200 OK

Slide 50

Slide 50 text

19 Возвращать или рейзить ○raise для ответов с ошибками ○raise для ответов с файлами ○return – для ответов 200 OK ●ошибки из логики в стиле Go

Slide 51

Slide 51 text

20 Набор общих мидлварей steps billing_api = APIView( name='billing', steps=[ steps.consumer_keys_auth, # (view, ctx: dict) -> dict steps.validation, steps.rate_limit, steps.lock, ], consumer_keys=[settings.BILLING_CONSUMER_KEY], ) BillingApiView = billing_api.View billing_api_method = billing_api.method

Slide 52

Slide 52 text

20 Набор общих мидлварей steps steps=[ steps.consumer_keys_auth, # (view, ctx: dict) -> dict steps.validation, steps.rate_limit, steps.lock, ], billing_api = APIView( name='billing', consumer_keys=[settings.BILLING_CONSUMER_KEY], ) BillingApiView = billing_api.View billing_api_method = billing_api.method

Slide 53

Slide 53 text

21 Railway Oriented Programming aka stories

Slide 54

Slide 54 text

22 Deep and shallow modules API View logic old school logic

Slide 55

Slide 55 text

23 В итоге

Slide 56

Slide 56 text

23 В итоге ●сломали почти каждого потребителя

Slide 57

Slide 57 text

23 В итоге ○сломали почти каждого потребителя ●зато есть OpenAPI спека

Slide 58

Slide 58 text

23 В итоге ○сломали почти каждого потребителя ○зато есть OpenAPI спека ●наклепали ещё с десяток апишек довольно быстро

Slide 59

Slide 59 text

23 В итоге ○сломали почти каждого потребителя ○зато есть OpenAPI спека ○наклепали ещё с десяток апишек довольно быстро ●быстро вставили трассировку

Slide 60

Slide 60 text

23 В итоге ○сломали почти каждого потребителя ○зато есть OpenAPI спека ○наклепали ещё с десяток апишек довольно быстро ○быстро вставили трассировку ●структурно не трогали уже 3 года

Slide 61

Slide 61 text

24 Зачем?

Slide 62

Slide 62 text

24 Зачем? ●Django не серебрянная пуля

Slide 63

Slide 63 text

24 Зачем? ○Django не серебрянная пуля ●свой фреймворк полностью под контролем

Slide 64

Slide 64 text

24 Зачем? ○Django не серебрянная пуля ○свой фреймворк полностью под контролем ●а надо было на Go переписать

Slide 65

Slide 65 text

25 Возможные вопросы ○Когда опенсорсить будешь? ○Какие вообще планы на этот фреймворк дальше? ○После всего этого что бы выбрал в качестве фреймворка? ○Зачем это вообще? ○Почему не FastAPI? ○Почему так нравится Django?

Slide 66

Slide 66 text

Вопроооосы ○💼 Карьера в Островке ○🎬 YouTube-канал «Ostrovok! Tech» ○🎙️ Подкаст «Два Ивана (название обсуждается)» ○📝 t.me/biozz_dev 26