Как Java-роботы видят Python

Паша Финкельштейн, JetBrains

asm0dey @ Telegram, Facebook, etc

asm0di0 @ Twitter

@asm0di0 at Twitter  @asm0dey at Telegram

Кто я

  • 13 лет в IT
  • 11 лет в разработке
    • Почти всё время на JVM
  • Полгода 😱 в Python
@asm0di0 at Twitter  @asm0dey at Telegram
@asm0di0 at Twitter  @asm0dey at Telegram

Я ❤️ Python

@asm0di0 at Twitter  @asm0dey at Telegram

Python 🐍💗 меня

@asm0di0 at Twitter  @asm0dey at Telegram

Как всё устроено в Java

  • Скучно
  • Все всё понимают
@asm0di0 at Twitter  @asm0dey at Telegram

Сложная логика?

  • Всё ещё скучно
  • Слишком просто
@asm0di0 at Twitter  @asm0dey at Telegram

Фичефлаги?

  • Ну вы поняли
@asm0di0 at Twitter  @asm0dey at Telegram

Django

  • А где логика?

drop-shadow

@asm0di0 at Twitter  @asm0dey at Telegram
@asm0di0 at Twitter  @asm0dey at Telegram

drop-shadow height:250px

drop-shadow height:250px

@asm0di0 at Twitter  @asm0dey at Telegram

Звонок другу!

https://phalt.github.io/django-api-domains/

def get_book(*, id: uuid.UUID) -> Book:
    book = Book.objects.get(id=id)
    author = AuthorInterface.get_author(id=book.author_id)
    return {
        'name': book.name,
        'author_name': author.name,
    }

django-best-practices/Make'em Fat

@asm0di0 at Twitter  @asm0dey at Telegram
@asm0di0 at Twitter  @asm0dey at Telegram

Где логика, Джанго?

height:400px drop-shadow

@asm0di0 at Twitter  @asm0dey at Telegram

Серьёзные ребята знают как делать правильно!

github.com/mdn/kuma/blob/master/kuma/authkeys/models.py

from django.utils.translation import ugettext_lazy as _

def generate_key():
    """Generate a random API key."""
    ...
@asm0di0 at Twitter  @asm0dey at Telegram

Продолжаем разбирать

class Key(models.Model):
    """Authentication key"""
    ...

    
    def generate_secret(self):
        self.key = generate_key()
        secret = generate_key()
        self.hashed_secret = hash_secret(secret)
        return secret
@asm0di0 at Twitter  @asm0dey at Telegram

А что не так-то?

  • А что если нам понадобится поменять алгоритм шифрования?
  • Логгирование прямо в модели?
  • Сложную логику создать невозможно
  • Транзакциями управлять тоже невозможно
@asm0di0 at Twitter  @asm0dey at Telegram

В Spring

// View
@Controller class MyController {
    @Inject MyService service;
    @GetRequest("/deal") UUID deal(@Valid Deal deal){
        service.saveDeal(deal);
@asm0di0 at Twitter  @asm0dey at Telegram

В Spring

// Controller
@Transactional
@Service class MyService {
    @Inject Repo1 repo1; @Inject Repo2 repo2;
    @Inject Repo3 repo3; @Inject Repo4 repo4;
    Result<UUID> deal(Deal deal){
        if(/* check */) {}
        else(/* check */) {
            repo1.save(/* */);
            repo2.save(/* */);
        }
        return /**/;
@asm0di0 at Twitter  @asm0dey at Telegram

В Spring

// Model
@Repository class MyService {
    @Inject Datasource ds;
    UUID save(Deal deal){
        try(var con = ds.getConnection()){
            var stmt = con.createStatement("INSERT INTO … RETURNING");
            stmt.fetchResult();
            return /* */;
@asm0di0 at Twitter  @asm0dey at Telegram

На что обратить внимание

  • DI
  • Разделение ответственности
  • Result<UUID>
@asm0di0 at Twitter  @asm0dey at Telegram

Если вы уже 😍 DI

dry-python/dependencies

class MyController(Injector):
    my_service = MyService

class MyService(Injector):
    repo1 = Repo1
    repo2 = Repo2

class Repo1(Injector):
    data_source = DataSource
@asm0di0 at Twitter  @asm0dey at Telegram

На слои поделили

Чтобы не сломать можно использовать seddonym/import-linter

[importlinter]
root_package = mypackage

[importlinter:contract:1]
name = My three-tier layers contract
type = layers
layers=
    mypackage.endpoint
    mypackage.service
    mypackage.repository
@asm0di0 at Twitter  @asm0dey at Telegram

Если вы уже 😍 Result<UUID>

dry-python/returns

@asm0di0 at Twitter  @asm0dey at Telegram

Сложно управлять транзакциями?

drop-shadow height:480px

@asm0di0 at Twitter  @asm0dey at Telegram

Транзакции — это не пара BEGIN-COMMIT а бизнес-сущность

@asm0di0 at Twitter  @asm0dey at Telegram

Есть сторис, который позволяет описать безнес путь

class Subscribe:
    @story
    @arguments('category_id', 'profile_id')
    def buy(I):
        I.find_category
        I.find_profile
        I.check_balance
        I.persist_subscription
        I.show_subscription
@asm0di0 at Twitter  @asm0dey at Telegram

Django transactions

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

Опять на View 🤦🏽‍♂️

transaction.atomic

@asm0di0 at Twitter  @asm0dey at Telegram

В джаве стандартизировано ВСЁ

  • Транзакции - JTA
    • JMS - работа с message-брокерами
    • JDBC - работа с БД
  • JSP, JSF — стандарты бэкэнд-рендеринга страниц
    И это надёжная основа
@asm0di0 at Twitter  @asm0dey at Telegram

Но не будем о грустном

Поговорим об исключениях

  • Или это тоже грустное?

  • В Python все исключения unchecked: фиг поймаешь.

  • В Java можно поделить

    • Но множество «важных» исключений — checked: фиг откажешься ловить
@asm0di0 at Twitter  @asm0dey at Telegram

И что?

void someFun(){
    try {
        instance.call(payload);
    } catch (IOException e){
        logger.info("Can't write to FS");
    } catch (UnknownUserException e){
        logger.info("Who am I?")
    }
    // etc, etc…
}
@asm0di0 at Twitter  @asm0dey at Telegram

И только Kotlin ❤️

Всё unchecked, но если очень надо™ — то есть аннотация @Throws

@asm0di0 at Twitter  @asm0dey at Telegram

Экосистема

@asm0di0 at Twitter  @asm0dey at Telegram

Я долго ругал django

Но есть и хорошие части!

  • Комьюнити огромно!
  • Django ORM тупит? django-perf-rec
    def test_home(self):
        with django_perf_rec.record():
            self.client.get('/')
    
    записывает все запросы в .yml файл
  • django-stubs Добавляет типы в django! 😍
@asm0di0 at Twitter  @asm0dey at Telegram

На Moscow Python Conf ++

Виталий Брагилевский о типах 👍

@asm0di0 at Twitter  @asm0dey at Telegram

Все понимают что типы нужны

@asm0di0 at Twitter  @asm0dey at Telegram

Что не так с PyPI

  • Зависимость добавляется так:
  • А куда она добавится?
  • А что подтянется вместе с ней?
  • А конфликтует ли она с моими зависимостями?
  • Совместима ли она с моей версией Python? Как добавить старую версию?
  • Безопасна ли она?
@asm0di0 at Twitter  @asm0dey at Telegram

Безопасна?

Если нам повезёт — мы будем устанавливать приложение глобально
Если нам повезёт — в setup.py будет аналог rm -rf /*

https://pages.charlesreid1.com/dont-sudo-pip/

А даже если не sudo — эта штука может сотворить всё что угодно!

И от этого не спастись!

@asm0di0 at Twitter  @asm0dey at Telegram

Типы упаковки

  • Egg. Умер, был нестандартизирован, но зато сравнительно безопасен
  • Wheel. Жив и опасен
    Хочется лучшего из обоих миров…
@asm0di0 at Twitter  @asm0dey at Telegram

Или выход есть?

pip install safety 👹👻

Эта штука может проверить уже добавленные библиотеки 😄

Но в целом Safety - это линтер. А линтеры — это хорошо.

@asm0di0 at Twitter  @asm0dey at Telegram

Какие ещё есть?

  • bandit - статический анализ security
  • dlint - ещё сколько-то проверок
@asm0di0 at Twitter  @asm0dey at Telegram

На Moscow Python Conf ++

Никита Воронов про ад с зависимостями 🔥

@asm0di0 at Twitter  @asm0dey at Telegram

О чём я говорил

  1. Python прекрасен!
  2. Экосистема огромна
  3. Уже очень много чего есть — надо только уметь искать :)
  4. То чего не хватает — можете добавить вы, комьюнити!
  5. Творите добро!
@asm0di0 at Twitter  @asm0dey at Telegram

Спасибо!

Вопросы?

asm0dey @ Facebook, Telegram
asm0di0 @ Twitter
it.asm0dey.ru
asm0dey@asm0dey.ru