Deploy de aplicações SSR utilizando o Supervisord

Se você está construindo aplicações que renderizam do lado do servidor, o famoso SSR, utiliznado React (com o next.js) ou Vue.js (com o nuxt.js), você vai precisar de alguma ferramenta para controle do processos quando for fazer o deploy. Eu escolhi fazer o deploy de aplicações SSR utilizando o Supervisord, apesar de ter visto em vários sites o pessoal utilizando PM2 para este tipo de aplicação, acredito que o Supervisord é mais conhecido em um contexto geral, não só de quem é do mundo do node.
Se você já viu meu Guia para Deploy Django e Python 3, você já usou o Supervisor.

A razão número 1 para você ter um aplicativo React ou Vue utilizando SSR é por causa do SEO. O Googlebot não trabalha bem com páginas renderizadas do lado do cliente (CSR, o contrário do SSR) e por conta disso pode nem indexar suas páginas. Então, ter uma aplicação SSR dessas rodando no seu servidor, significa que você vai servir a aplicação utilizando o node pra rodar os javascripts que você criou. E para manter o seu comando node rodando, você não pode simplesmente abrir em um screen e torcer para que continue rodando. Você precisa colocar seu app em uma ferramenta de controle de processos como o Supervisord para inicializar sua aplicação caso o servidor reinicie ou sua própria aplicação dê algum pau.

Instalando o Supervisord:

sudo apt-get install supervisor

Agora, vamos criar um arquivo de configuração para a aplicação SSR:

sudo vi /etc/supervisor/conf.d/my-ssr-app.conf

That’s the content:


[program:myappname]
directory=/home/username/yourproject/
command=npm run start
user=username
autostart=true
autorestart=true

Então, precisamos avisar o supervisord que existe um novo processo para ele controlar


sudo supervisorctl reread
sudo supervisorctl update

E futuramente, quando precisar restartar apenas o seu app, use o nome que você colocou no arquivo de configuração.


sudo supervisorctl restart myappname

E é isso aí. Agora você sabe como fazer deploy de aplicações SSR utilizando o Supervisord.

I Know Kung Fu GIF from The Matrix (Now you know how to deploy ssr applications using supervisord)

Alguns links:

Post original em inglês

https://www.reddit.com/r/reactjs/comments/an16mx/how_does_seo_work_with_react/

https://medium.com/walmartlabs/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8

https://dev.to/stereobooster/server-side-rendering-or-ssr-what-is-it-for-and-when-to-use-it-2cpg

Criando um container Docker para um projeto Django Existente

Neste post, vou mostrar como criar um container Docker para um projeto Django já existente. Como exemplo, resolvi buscar por uma issue aberta no github que estivesse pedindo para ser dockerizada. Criei um PR para a Issue e usei como exemplo aqui.

Por quê você vai querer dockerizar uma aplicação web django que já existe? Bom, existem muitas razões, se você acha que não tem uma, faça pela diversão!

Eu decidi usar o docker em uma das minhas aplicações porque ela estava ficando muito difícil de instalar. Muitos requisitos do sistema, vários bancos de dados, celery, rabbitmq e por aí vai. Sem dockerizar cada vez que uma nova pessoa entra no time é um inferno pra setar tudo porque levava tempo demais.

O mundo ideal é que o programador tenha em seu ambiente de desenvolvimento o mais próximo que puder do ambiente de produção. Se você usa SQLite na sua máquina mas Postgres no servidor pode ser que tenha problemas de dados que são simplesmente truncados localmente mas que vão levantar erros na base de produção. Só para ter ideia de um exemplo.

Se você não sabe o que é o docker, imagine que é um imenso virtualenv que no lugar de ter apenas pacotes python tem o sistema operacional “inteiro”. Isso consegue isolar seu app de tudo que está no seu SO, bancos de dados, workers, etc.

Mão na massa

Ok, falar é fácil, vamos codar um pouco.

Primeiro de tudo, instale o Docker. Fiz isso no Ubuntu e no Mac sem nenhum problema. Já no Windows Home não consegui fazer funcionar.

Para dizer ao docker que sua aplicação é um container você precisa criar um arquivo Dockerfile:



FROM python:3.6
ENV PYTHONUNBUFFERED 1

RUN mkdir /webapps
WORKDIR /webapps

# Installing OS Dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libsqlite3-dev

RUN pip install -U pip setuptools

COPY requirements.txt /webapps/
COPY requirements-opt.txt /webapps/

RUN pip install -r /webapps/requirements.txt
RUN pip install -r /webapps/requirements-opt.txt

ADD . /webapps/

# Django service
EXPOSE 8000

Vamos lá, linha a linha:

Docker Images

FROM python:3.6

Aqui estamos usando uma imagem do docker hub. Isto, é um container pré-formatado do docker que permite que você monte sua máquina a partir daquela configuração inicial. Nesse caso, Python 3.6 é um container de um Ubuntu que tem o Python 3.6 instalado nele. Você pode procurar por containers no docker hub.

Environment Variables (Variáveis de ambiente)

Você pode criar todas as variáveis de ambiente que quiser com o ENV.

ENV PYTHONUNBUFFERED 1  # Here we can create all Environment variables for our container

Por exemplo, se você usa variáveis de ambiente para guardar sua secret key do Django é só fazer assim:

ENV DJANGO_SECRET_KEY abcde0s&&$uyc)[email protected]!a95nasd22e-dxt^9k^7!f+$jxkk+$k-

E usar no seu settings, desse jeito:

import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']

Run Commands

Docker Run Commands tem um nome meio óbvio. Você pode rodar um comando “dentro” do seu container. Estou colocando dentro entre aspas porque o docker na verdade cria algo como sub containers para que não precise rodar os mesmos comandos novamente no caso de precisar dar um rebuild do container.

RUN mkdir /webapps
WORKDIR /webapps

# Installing OS Dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libsqlite3-dev

RUN pip install -U pip setuptools

COPY requirements.txt /webapps/
COPY requirements-opt.txt /webapps/

RUN pip install -r /webapps/requirements.txt
RUN pip install -r /webapps/requirements-opt.txt

ADD . /webapps/

Aqui estamos criando o diretório que guardará os arquivos do projeto: webapps/.

Workdir é uma instrução para mostrar ao docker em qual diretório ele vai rodar os comandos a partir dali.

Depois disso estou instalando as dependências do sistema operacional. Quando estamos usando requirements.txt no projeto, estamos colocando apenas os requisitos do python e não os do SO. Mais um motivo para querer usar o docker para desenvolver. Quanto maior seu projeto, mais requisitos de sistema operacional vão aparecer.

COPY e ADD

Copy e ADD são basicamente a mesma cosia. Ambos copiam um arquivo do seu computador (o Host) dentro do container (o Guest). No meu exemplo, estou apenas copiando o requirements para dentro do docker, para que eu possa dar pip install nos pacotes.

EXPOSE

Expose é para mapear uma porta do Guest (o Container) para o Host (seu computador)

# Django service
EXPOSE 8000

Ok, e agora? Como podemos adicionar mais containers para rodá-los juntos? E se precisarmos colocar um postgresql para rodar em um container também? Não se preocupe, vamos usar o docker-compose.

Docker-Compose

O compose é uma ferramenta para rodar múltiplos containers do docker. Só precisa criar um arquivo yml na pasta do seu projeto com o nome docker-compose.yml:

version: '3.3'

services:
  # Postgres
  db:
    image: postgres
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=postgres

  web:
    build: .
    command: ["./run_web.sh"]
    volumes:
      - .:/webapps
    ports:
      - "8000:8000"
    links:
      - db
    depends_on:
      - db

Aqui estou usando uma imagem do Postgres que também peguei no Docker Hub.

Agora vamos mudar o settings.py para poder usar o Postgres do container como banco de dados.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': '5432',
    }
}

Quase lá, deixa eu falar um pouco sobre o arquivo docker-compose.yml,

VOLUMES

Lembra do vagrant?

Era uma vez o Vagrant. Ele era uma forma de rodar um projeto dentro de uma Máquina Virtual que permitia configurar e mapear portas fácilmente, provisionar requisitos do sistema e compartilhar volumes. Seu computador (o Host) podia compartilhar um volume com a máquina virtual (o Guest, ou convidado). No docker, o volume é exatamente a mesma coisa. Quando você escreve um arquivo em um volume que está compartilhado o arquivo também está sendo escrito dentro do container.

volumes:
  - .:/webapps

Nesse o caso, o diretório em que nos encontramos (.) é o que está sendo compartilhado com o container.

LINKS

links:
  - db

Você pode se referir a outro container que pertence ao seu arquivo docker-compose utilizando o nome dele. Como criamos um container com o nome db para o Postgres nós podemos criar um link para ele no nosso container chamado web. Pode ver que no settings.py nós colocamos ‘db‘ como host.

DEPENDS_ON

Para rodar sua aplicação, seu banco de dados precisa estar pronto antes do container web, senão vai dar algum pau!

depends_on:
  - db

Command

Command é o comando padrão que o docker vai rodar logo depois que você subir, ou seja, colocar os containeres para funcionar.

No nosso exemplo, eu criei um run_web.sh que vai rodar as migrações, coletar o static files e iniciar o servidor de desenvolvimento.

#!/usr/bin/env bash

cd django-boards/
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py runserver 0.0.0.0:8000

Alguém pode argumentar que migrar assim automaticamente toda vez que subir o container pode não ser uma boa prática. Eu concordo. Você pode (e deve) rodar o migrate direto na máquina web. Você pode acessar seu container para rodar comandos assim (como no bom e velho vagrant ssh):

docker-compose exec web bash

Se você quiser , você pode rodar o comando sem acessar o container mesmo, apenas mudando o último argumento do comando acima:

docker-compose exec web python manage.py migrate

O mesmo para outros comandos:

docker-compose exec web python manage.py test
docker-compose exec web python manage.py shell

Rodando o Docker

Com nosso Dockerfile, docker-compose.yml e o run_web.sh no lugar, vamos rodar tudo junto:

docker-compose up

Você pode ver esse projeto aqui no meu GitHub.

Escrevi esse texto originalmente em inglês.

**EDITADO**

Antes eu estava usando run no lugar de exec. Mas o Bruno FS me mostrou que o exec é melhor pois está acessando exatamente o container que já está rodando e que o run na verdade está criando um novo container.

References:

4 Melhores Podcasts sobre Tecnologia e Startups

Ouvir Podcasts é uma forma muito eficiente para se manter por dentro do que acontece na sua área.  Ouço vários Podcasts sobre tecnologia e startups, além de outros temas e tenho certeza que eles são importantes para que eu continue atualizado no meu ramo. Estar informado pode ser a diferença entre ser bom e ser ótimo.

Para quem não conhece, podcast é como um programa de rádio com a diferença de que você pode ouvir quando quiser, escolher os episódios e sempre ficará sabendo quando publicarem um programa novo. Podcast não é novidade mas não chegou a virar mainstream no Brasil ainda. Vai crescer muito este ano.

Meu aplicativo para assinar (baixar e ouvir) podcasts é o PocketCasts ele é pago, paga uma vez só e usa todos os dias, mas existem várias outras opções gratuitas aí para quem não quiser desembolsar os R$ 12 reais. Bora pra lista:

Hipsters.tech

logo do podcast hipsters.tech

Apesar do nome péssimo, clichê e modinha é na minha opinião o melhor podcast brasileiro. Tem um episódio por semana e é um programa muito bem formatado. Normalmente os episódios tem menos de uma hora e variam sobre desenvolvimento, design, empreendedorismo, etc.

Esse é um dos episódios que gostei bastante sobre Squads. Você pode dar uma olhada em outros episódios no site deles https://hipsters.tech

Software Engineering Daily

software engineering daily logo

Esse é um podcast gringo (em inglês). Eles soltam um episódio por dia útil, ou seja, é daily mesmo. Mesmo que não dê para ouvir todos os dias é bom ficar por dentro e olhar o que eles já fizeram. Tem entrevistas com gente da área de tecnologia e de startups, normalmente engenheiros e empreendedores de empresas renomadas e criadores das tecnologias que estão sendo discutidas. Você pode ver os episódios aqui

Talk Python to Me

talk python to me podcast logo

Esse podcast é mais específico para pythonistas, mas quem não gosta de python? Tem participações de caras muito fodas da comunidade como Kenneth Reitz, David Beasley e até do Guido Van Rossum.

O site é esse aqui: https://talkpython.fm/ . Meu único problema com esse podcast é que ele tinha a melhor música de abertura de qualquer podcast até o meio do ano passado, depois entrou uma música chata =P

Like a Boss

like a boss podcast logo

Esse é um podcast dos mesmos criadores do Hipsters. O objetivo é “trazer entrevistas com líderes, fundadores de startups e empresas inovadoras” nas próprias palavras deles. Está bem no comecinho, tem apenas 7 episódios.

Gostei bastante dessa entrevista com David Vélez, o fundador do Nubank.

Outros podcasts:

Eu gostava muito do ZOFE (Zone of Front-Enders) , mas infelizmente ele não tem novas publicações já faz tempo. O site está mais desatualizado que o podcast mas ainda assim dá para ouvir conteúdo do passado. Era realizado pelo Daniel Filho, um cara diferenciado que eu sempre via no melhor meetup de front-ends de São Paulo, o FEMUG-SP.

Falando em podcasts antigos, outro que curti bastante mas já não solta coisa nova é o Grok Podcast. Isso que é bom dos podcasts, eles podem ter acabado mas os episódios estão aí para sempre.

Um podcast que eu descobri recentemente foi o Castálio Podcast. Também fala bastante de Python. Por enquanto, só ouvi um episódio e foi sobre serverless. Aliás, dá pra ver o podcast sendo gravado ao vivo no youtube e mandar perguntas para eles.

Mais um podcast gringo que estou esperando ver como vai desenrolar é o Modern CTO. Me parece uma estratégia audaciosa para criar conteúdo para CTOs uma coisa que não se vê todo dia. Estou começando a acompanhar.

Bom, esses foram os meus podcasts favoritos sobre esses temas. Quem tiver outros podcasts que quiser indicar deixe nos comentários.

 

Populando um campo novo não-nulo no Django

Fala pessoal, esse é um post rápido no qual eu vou mostrar como popular uma migração criando um campo novo não-nulo no django

SAY WUUUUT?????

george costanza taking his glasses off

Seguinte, sabe quando você tem seu projeto rodando, banco de dados, models, front, em produção as porra tudo e então aparece um requisito novo e eis que surge um campo obrigatório, que ninguém pensou antes, nem o cliente, nem o product owner, nem ninguém! Essa é a situação!

Acontece que você usa as migrações do django e você quer que poder colocar esses campos usando os migrations, tanto migrando pra frente quanto pra trás, ok?

Chega de conversa, pra esse exemplo resolvi pegar um projeto pronto nas interwebz, acabei optando por um django polls feito em django 1.10 já pronto.

Então lá se vão os passos de sempre, clonar e criar virtualenv…

git clone [email protected]:garmoncheg/django-polls_1.10.git
cd django-polls_1.10/
mkvirtualenv --python=/usr/bin/python3 django-polls 
pip install django==1.10  # Nesse projeto o autor não criou um requirements.txt
python manage.py migrate  # rodando as migrações existentes
python manage.py createsuperuser 
python manage.py runserver

Obs: Esse projeto pronto que pegamos está com uma migração faltando, então se você estiver seguindo esse passo a passo rode um python manage.py makemigrations pra que seja criada a migração 0002 (que é só a mudança de um verbose_name)

Agora você acessa o admin (http://localhost:8000/admin/polls/question/add/) e cria o seu poll :

 

django polls admin
Aí você pode ir lá no app polls e ver sua pergunta, responder etc…
até aí OK, ainda não fizemos nada!

Bom, o ideal é criar mais umas perguntas com datas de publicação diferentes para a brincadeira
ficar boa.

Depois de começar a usar você vai perceber que qualquer enquete fica infinita no seu site, ou seja, todas as vezes que alguém entra no site ele tem a oportunidade de votar, você nunca encerra a votação.

Então nossa alteração no código será a seguinte: De agora em diante, todas as enquetes terão uma data de expiração. No momento do cadastro o usuário poderá colocar a data na qual a votação se encerrará e o usuário será direcionado diretamente para o resultado. Queremos que essa data de expiração seja um campo obrigatório! E para os que já estão funcionando na plataforma vamos determinar arbitrariamente que cada um tenha um mês de validade a partir da data de publicação.

Antigamente, antes de migrações como isso era feito em qualquer sistema? Via SQL você criava um campo que permitisse NULOS, depois você criava uma query que populava esse campo e por fim você alterava a tabela pra tornar aquela coluna obrigatória. Com as migrações é a mesma coisa.

Então vamos criar o campo novo nos models vou chamá-lo de expires_date:

expires_date = models.DateTimeField(‘expires at’, null=True)

E o model inteiro fica assim:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    expires_date = models.DateTimeField('expires at', null=True)

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'


Agora vamos criar a migração dessa mudança:

python manage.py makemigrations

E ele criará a migração 0003_question_expires_date. O conteúdo é o seguinte:

class Migration(migrations.Migration):

    dependencies = [
        ('polls', '0002_auto_20170429_2220'),
    ]

    operations = [
        migrations.AddField(
            model_name='question',
            name='expires_date',
            field=models.DateTimeField(null=True, verbose_name='expires at'),
        ),
    ]

 

Vamos modificar o código dessa migration, NO PANIC!

Populando o novo campo

Primeiro criamos uma função para popular o banco com as datas de expiração:

def populate_expires_date(apps, schema_editor):
    """
    Popula o campo data de expiração das perguntas já existentes colocando um mẽs de validade para cada.
    """
    from datetime import timedelta

    db_alias = schema_editor.connection.alias
    Question = apps.get_model('polls', 'Question')

    for row in Question.objects.using(db_alias).filter(expires_date__isnull=True):
        row.expires_date = row.pub_date + timedelta(days=30)
        row.save()

Originalmente usei esse código em um projeto que utiliza múltiplos bancos de dados, então precisei usar o db_alias e achei interessante deixá-lo aqui. Quanto ao Question, estou dando um import desse model usando o apps.get_model pois nesse momento que estamos rodando a migração o campo ainda não existe para o projeto, pois a migração não acabou de rodar, então é melhor que importar do model.

Agora, dentro da migração existe uma lista chamada operations. Nessa lista vamos adicionar os comandos para rodar a nossa função e em seguida, vamos adicionar a obrigatoriedade do campo: ficando dessa forma:

operations = [
    migrations.AddField(
        model_name='question',
        name='expires_date',
        field=models.DateTimeField(null=True, verbose_name='expires at'),
    ),
    migrations.RunPython(populate_expires_date, reverse_code=migrations.RunPython.noop),
    migrations.AlterField(
        model_name='question',
        name='expires_date',
        field=models.DateTimeField(verbose_name='expires at'),
    )
]

Você pode ver que utilizamos o migrations.RunPython para rodar nossa função durante a migração. O reverse_code serve quando alguém for dar um unapply da migração, nesse caso não existia o campo, então não faremos nada.

Logo em seguida adicionei a migração que altera o campo e ele deixa de ter null=True. Também poderíamos ter feito isso em outra migração, só retirando essa informação do model (que precisa ser retirada agora de qualquer forma) e ter criado uma nova migração.

O model ficará assim:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    expires_date = models.DateTimeField('expires at')

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'

agora você pode rodar o migrate tranquilamente:

python mange.py migrate

Pronto! Pra ver as alterações vou adicionar esse campo no admin, tanto para ser editado como no list_display:

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date', 'expires_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]
    list_display = ('question_text', 'pub_date', 'expires_date', 'was_published_recently')
    list_filter = ['pub_date']
    search_fields = ['question_text']

E voilá, todos as Questions que você tinha no polls contam com um expires_date, obrigatório e com 30 dias adicionado por default para as Questions antigas.

É isso aí, agora tem esse campo que queremos! O projeto com as alterações está aqui: https://github.com/ffreitasalves/django-polls_1.10

Se gostou, compartilhe com os amigos e deixe um comentário. Se não gostou também! Abraços!

 

Fontes:

http://stackoverflow.com/questions/28012340/django-1-7-makemigration-non-nullable-field

http://stackoverflow.com/questions/29217706/django-view-sql-query-without-publishinhg-migrations

https://realpython.com/blog/python/data-migrations/

https://docs.djangoproject.com/en/1.10/howto/writing-migrations/#non-atomic-migrations

2o Hangout Empreendedorismo Python Brasil

O pessoal do grupo Python Brasil está começando a fazer hangouts semanais pra falar sobre Python e Empreendedorismo e hoje participei do 2o Hangout. Falei um pouco sobre minha experiência e tentei falar alguma coisa sobre a importância do programador também ser um empreendedor.

Tentei falar, porque eu não tinha planejado o que falaria, só rabisquei alguns tópicos no caderno, mas aqui no blog eu posso detalhar um pouco o que eu pensei e colocar uns links também.

Esqueci de citar um dos links principais que gostaria, que é o curso How to Start a Startup do Sam Altman, CEO do YCombinator

Vídeo da Apresentação:

 

Curso

Notícias

Links dos Projetos

Podcasts

Livros

Outros Links

Bônus

Celery com múltiplas filas, retry e tarefas agendadas

No ano passado eu escrevi um post sobre Tarefas demoradas de forma assíncrona com Django e Celery, que serve para a maioria dos casos de uso do celery. O problema é que você pode querer e fazer muito mais com o celery, como ter filas diferentes para tarefas prioritárias, executar um retry após a falha de uma tarefa ou agendar a tarefa para ser executada apenas em outro horário ou outro dia.

Retry em uma Tarefa

Suponhamos que isso seja uma caveira que sua tarefa depende de uma API externa, conexão com algum site ou algum outro motivo que possa levá-la a um ConnectionError, por exemplo.

É provável que dentro de alguns instantes a API, site, etc. esteja no ar e que se você executar sua tarefa novamente ela funcione. Para isso podemos agendar um retry para esta tarefa:


from celery import shared_task
 
@shared_task(bind=True, max_retries=3)
def acessar_sistema_horrivel(self, meu_objeto_id):
    from core.models import MeuObjeto
    from requests import ConnectionError
 
    objeto = MeuObjeto.objects.get(pk=meu_objeto_id)
 
    # Se tiver ConnectionError tenta de novo em 180 segundos
    try:
 
        objeto.acessar_sistema_horrivel()
  
    except ConnectionError as exc:
        self.retry(exc=exc, countdown=180)
 
 

Interessante que dentro de uma função você está usando self.retry. Isso é possível graças ao bind=True no decorator do shared_task, que faz com que nossa função acessar_sistema_horrivel seja na verdade um método da classe Task. Isso nos obriga a colocar self como primeiro argumento da função também.

O número máximo de tentativas para executar essa tarefa é o max_retries=3 definido no decorator

ETA – Agendando a tarefa para começar mais tarde

Agora o cenário é o seguinte, seu sistema vai chamar uma tarefa assíncrona, mais quer que ela seja executada apenas daqui a uma hora.

Para isso podemos chamar a task setando a propriedade ETA (estimated time of arrival) e isso significa que sua tarefa só será executada depois daquele horário (não necessariamente para começar aquele horário, isso vai depender se você possui workers disponíveis, para agendar exatamente como no cron você pode usar o CeleryBeat) .

 


from django.utils import timezone

agora = timezone.now() 
 
# Depois é daqui a uma hora 
#(usei o replace pq quis mostrar, mas vc poderia usar o timedelta para somar também)
depois = agora.replace(hour=agora.hour + 1)
 
acessar_sistema_horrivel.apply_async((meu_objeto_id), eta=depois)
 

Colocando mais de uma fila de execução

Quando você executa o celery ele cria uma fila no seu broker (no caso do post anterior, era o RabbitMQ). A fila padrão chama celery. Qual a implicação? Se você possui várias tarefas assíncronas a serem executadas ficam todas na mesma fila, das qual os workers vão pegar as mensagens e executar.

Imagine que temos além da task acessar_sistema_horrivel, uma outra task chamada tarefa_demorada.

Então dado o seguinte cenário:

3 tarefas: uma lenta e uma mais rápida e a acessar_sistema_horrivel
1 fila
4 workers

E imagine que o sistema produziu 10 requisições da tarefa_demorada para serem executadas e em seguida produziu mais 10 da outra tarefa. O que vai acontecer? Seus workers ficarão todos ocupados buscando executando as requisições de tarefa_demorada que entraram primeiro e nenhum estará trabalhando na tarefa mais rápida.

A solução para isso é produzir filas diferenciadas para as tarefas prioritárias.


# CELERY ROUTES
CELERY_ROUTES = {
    'core.tasks.acessar_sistema_horrivel': {'queue': 'fila_horrivel'},
    'core.tasks.tarefa_demorada': {'queue': 'fila_demorada'},
    'core.tasks.tarefa_mais_rapida': {'queue': 'fila_rapida'},
}

Isso é suficiente para que as mensagens para essas tarefas sejam enviadas para essas filas do RabbitMQ. Detalhe, essas filas são criadas de forma permanente no RabbitMQ, mas isso também pode ser configurado.

Agora para rodar os consumidores para essas filas quando formos executar o celery podemos definir com o parâmetro -Q. No caso fica assim:


celery --app=nome_projeto worker -Q fila_horrivel,fila_demorada,fila_rapida --autoscale=10,5

Estamos iniciando os workers para todas essas filas. Neste caso estou colocando também o parâmetro autoscale, indicando para trabalhar com no mínimo 5 workers e caso haja muitas tarefas o celery adiciona mais workers até o máximo de 10. Isso é bom para economizar recursos do seu sistema, caso você tenha horários com mais produção de tarefas.

Como mostramos no outro post, para usar isto sob o supervisord, você pode modificar aquele mesmo script


[program:celery]
command=/home/deploy/.virtualenvs/meu_virtual_env/bin/celery --app=nome_projeto worker -Q fila_horrivel,fila_demorada,fila_rapida --autoscale=10,5
directory=/home/deploy/webapps/projeto_django
user=nobody
autostart=true
autorestart=true
redirect_stderr=true

Em seguida rodar o reread e o update, porque o arquivo de configuração mudou e reiniciar o supervisord para esse arquivo.


sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart gunicorn

Com isso seus workers vão executar tarefas de cada uma das filas, não travando os workers para só um tipo de tarefa. Há diversas outras formas de colocar seus workers para funcionar.

Chamando Tarefas Sequenciais

Outro problema comum, é precisar chamar duas tarefas assíncronas , uma depois da outra. Isso pode acontecer em diversos cenários, ex: o caso de a segunda tarefa receber como parâmetro o resultado da primeira, ou da primeira tarefa salvar algum dado e a segunda tarefa trabalhar aquele dado, etc…

Para isso nós podemos usar o Chain, que é o recomendado ao invés de chamar a segunda tarefa assíncrona com o delay dentro da primeira tarefa.


from celery import chain
from tasks import salvar_dados, trabalhar_dados
 
# Estamos chamando o método s das tasks, que as usa como subtasks de uma task gerada pelo chain.
chain(salvar_dados.s(meu_objeto_id) | trabalhar_dados.s())

A tarefa trabalhar_dados vai utilizar o return ou resultado da tarefa salvar_dados como parâmetro.
O chain em sí está gerando uma task, então para ele também serve chamar o apply_async com um eta definido, por exemplo:


chain(salvar_dados.s(meu_objeto_id) | trabalhar_dados.s()).apply_async(eta=depois)

Ignorando os resultados do ResultBackend

Se você só usa as tasks para executar alguma coisa que não precisa do return da task, você pode ignorar o ResultBackend e ter um ganho de performance.

A maioria dos casos não precisa guardar o resultado do return, então é só uma dica interessante se você já está salvando algo nos models, ou fazendo whatever diretamente dentro da sua task, coloque isso no seu settings.py:


CELERY_IGNORE_RESULT = True

 

Na verdade tem muito mais coisas legais no Celery, que ficarão pra um próximo post.

Fontes

http://docs.celeryproject.org/en/latest/userguide/tasks.html

http://docs.celeryproject.org/en/latest/userguide/optimizing.html#guide-optimizing

https://denibertovic.com/posts/celery-best-practices/

https://news.ycombinator.com/item?id=7909201

http://docs.celeryproject.org/en/latest/userguide/workers.html

http://docs.celeryproject.org/en/latest/userguide/canvas.html

 

Super Bônus

Celery Messaging at Scale at Instagram – Apresentação Pycon 2013

Tarefas demoradas de forma assíncrona com Django e Celery

Estou escrevendo esse post empolgado por uma lightning talk que fiz no GruPy-SP (16/07). Assim aproveito pra explicar um pouco mais o que estou dizendo nos slides.

O problema de tarefas demoradas do lado do servidor

Sempre que o cliente faz uma requisição web (request), o servidor faz um processamento, normalmente pra quem usa o django, ele lê a requisição, trata os dados recebidos, salva ou recupera registros do banco de dados (através dos models), faz algum processamento do que será exibido para o usuário, renderiza isso em um template e manda uma resposta (response) para o cliente.

Dependendo da tarefa que você executa no servidor a resposta pode demorar muito e isso leva à problemas de TimeOut, a experiência do usuário fica comprometida (quanto mais o servidor demorar pra responder maior a chance do usuário sair do seu site) e também pode acontecer da lentidão ser causada por outros, como por exemplo o uso de uma API externa.

Existem diversas tarefas que podem demorar pra ser executadas. Se você tiver que criar um relatório pesado, acionado por um client web. Se de repente você precisar enviar diferentes emails para uma lista ou por exemplo editar um vídeo depois que o usuário faz o upload na sua página.

Caso real

Esse é um problema que me deparei um dia na geração de um relatório. O relatório levava cerca de 20 minutos para ser gerado, o cliente recebia um erro de timeout e obviamente não dá pra esperar 20 minutos pra gerar alguma coisa. Pra driblar isso e deixar a tarefa em background, alguém resolveu usar um comando do sistema operacional. (No linux se você colocar & no final de um comando esse comando roda em background).

django-view-os-system

Note que em seguida o django envia mostra uma mensagem ao usuário de que ele será avisado por e-mail ao final da geração do relatório.
Alguma coisa não parece certa aqui.

Me arrependo imediatamente dessa decisão

Celery – A solução para esses problemas!

O Celery é um gerenciador de tarefas assíncronas. Com ele você pode executar uma fila de tarefas (que ele recebe por meio de mensagens), pode agendar tarefas direto no seu projeto sem precisar do cron e ele ainda tem integração fácil com a maioria dos frameworks python mais utilizados (django, flask, tornado, etc.)

Como o Celery funciona

Essa imagem kibada da internet dá uma visão geral de como fica o fluxo da sua aplicação.

estrutura-celery

  • O User (ou Client ou Producer) é a sua aplicação Django.
  • O AMPQ Broker é um Message Broker. Um programa responsável por manter a fila de mensagens que serão trocadas entre o seu programa e o Celery, geralmente é o RabbitMQ ou o Redis
  • Os workers (ou consumers) que vão executar as tarefas que você quer que sejam assíncronas.
  • O Result Store, que é onde os workers podem ou não salvar os resultados das tarefas que foram executadas.

O Client pode passar uma tarefa, uma lista de tarefas, tarefas periódicas ou o retry de alguma tarefa pra fila do Message Broker. O Message Broker distribui essas tarefas entre os workers e o resultado dessas tarefas pode ser escrito em um Result Store (Memcahed, RDBMS, MongoDB, etc…) que mais tarde pode ser lido pelo Client novamente.

Qual Broker utilizar?

O recomendado pelo Celery é o RabbitMQ.

Instalando e configurando o RabbitMQ

Existem vários exemplos de como utilizar o Redis, eu vou mostrar como usar o RabbitMQ.

Você só precisa:

  1. Instalar o RabbitMQ (Baixar o pacote no site ou adicionar o repo deles no sources.list e instalar com APT-GET)
    sudo apt-get install rabbitmq-server
  2. Criar um usuário, um virtual host e dar permissões para esse usuário no virtualhost:
    
    sudo rabbitmqctl add_user myuser mypassword
    sudo rabbitmqctl add_vhost myvhost
    sudo rabbitmqctl set_permissions -p myvhost myuser ".*" ".*" ".*"
    

Instalando e configurando o Celery

pip install celery

No seu settings.py:

#Celery Config
BROKER_URL = 'amqp://guest:[email protected]:5672//'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'

Na pasta do projeto (a mesma pasta do settings.py) crie um arquivo chamado celery.py:


from __future__ import absolute_import

import os
from celery import Celery
from django.conf import settings

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nome_do_proj.settings')

app = Celery('nome_do_proj')

# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Esse autodiscover_tasks permite que seu projeto ache todas as tarefas assíncronas de cada app instalada nele. Nessa mesma pasta do settings.py, você vai modificar o arquivo de pacote __init__.py:


from __future__ import absolute_import

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

Criando as tarefas assíncronas do seu app

Na pasta do seu app crie um arquivo chamado tasks.py:

from __future__ import absolute_import
from celery import shared_task
from relatorios import gerar_relatorio_excel


@shared_task
def gerar_relatorio(data_inicial, data_final, email):
    gerar_relatorio_excel(
        data_inicial = data_inicial,
        data_final = data_final,
        email = email
    )
    return "Relatorio Excel gerado"

Agora é só fazer importar essa função que foi criada usando o método delay que foi adicionado à sua função pelo decorator shared_task.

tabelao assincrono

Rodando o Celery

Agora você precisa rodar o celery. Ele vai rodar os seus workers pra executar suas tarefas de forma assíncrona. Por padrão ele inicia um worker para cada núcleo de processador, mas você pode determinar quantos com -c Numero_de_workers

celery --app=nome_projeto worker --loglevel=INFO

E então você verá essa tela:

celery-rodando

Você pode então testar sua função assíncrona com o shell do django mesmo:

chama funcao

E o que aparece na tela que está mostrando o celery:

aparece no celery

Colocando o Celery em Produção

Para usar o Celery em produção eu recomendo que você use o Supervisor

Para instalar o supervisor:

sudo apt-get install supervisor

Depois crie um arquivo de configuração para o celery e coloque um link simbólico para esse arquivo no /etc/supervisor/conf.d/

[program:celery]
command=/home/deploy/.virtualenvs/meu_virtual_env/bin/celery --app=nome_proj worker --loglevel=INFO
directory=/home/deploy/webapps/projeto_django
user=nobody
autostart=true
autorestart=true
redirect_stderr=true

Para o supervisor ler novamente os arquivos que estão em sua pasta de configuração:

sudo supervisorctl reread
sudo supervisorctl update

Agora é só iniciar o celery:

sudo supervisorctl start celery

Bom, acho que é só isso. Esses são os slides da minha apresentação:

Continuação

Celery com múltiplas filas, retry e tarefas agendadas

Fontes

E aqui vão as fontes de pesquisa (ou algumas) que eu utilizei nesse processo:

Bônus:

Como o Feed do Instagram usa o Celery e o RabbitMQ

Como criar um instalador MSI utilizando o Inno Setup

Essa é a terceira parte da série de Posts Distribuir Programas em Python para Desktops Windows. Nela você vai aprender como criar um instalador MSI utilizando o Inno Setup e colocando as DLLs do MSVCR para o Python funcionar em qualquer máquina windows.

As outras duas partes são:

Bom, nós vimos como criar um instalador MSI com o cx_freeze e utilizando as DLL’s de MSVCR da própria página no primeiro post, depois vimos como modificar o programa para ele ter um Auto-Update. Só que quando alteramos o setup.py pra gerar o programa com o Auto-Update não conseguimos mais fazer um instalador com o cx_freeze. Isso acontece porque o Esky modifica o seu programa, ele cria um executável que verifica se há algum download disponível no FTP, se tiver ele faz o download, faz o update, verifica se deu certo e exclui as bibliotecas antigas.

Primeira coisa: baixar e instalar o Inno Setup.

O Inno Setup cria um arquivo .iss que nada mais é que um script para criar o seu instalador. Abrindo o Inno você tem a opção de escrever esse script diretamente ou usar o Script Wizard (Um assistente):

inno

 

Nós vamos criar um instalador usando o assistente, primeiro nós vamos utilizar aquele arquivo que geramos no post anterior, o boneca-1.0.1.win32.zip. Você precisa ter descompactado esse arquivo:

boneca-esky-conteudo

No Inno Setup clique em File >> New. Você abrirá o Assistente, ele é fácil de usar, preencha esses dados como quiser:

assistente-inno-setup-1

Na próxima tela você pode escolher onde o Instalador vai colocar o seu programa. O Default é o Program Files folder, mas tem um porém: Se o seu código não tiver Assinatura Digital (Code Signing) você vai ter dificuldades com o UAC do Windows, porque ele não vai conferir a autenticidade do seu código e isso implicará em problemas com antivírus, em bloqueio do programa pelo windows e principalmente ele pode impedir que o programa faça o auto-update, por isso use uma outra pasta a princípio. Você pode escrever um caminho ou usar uma Constante das pastas do Windows do próprio Inno Setup. [Recomendável]

assistente-inno-setup-2

Na terceira tela você vai colocar quais os programas, pastas e arquivos serão instalados pelo instalador, no caso o boneca.exe e o python27.dll ficaram na raiz e também a pasta boneca-1.0.1.win32 com o conteúdo dela.

Então coloque boneca.exe no campo de Application main executable file
Em seguida, clique em Add File para colocar o python27.dll e depois em Add Folder para adicionar a pasta boneca-1.0.1.win32:

assistente-inno-setup-3

Agora é só seguir em diante no assistente, no final ele vai criar o arquivo .iss. Você já pode compilar o arquivo e ele vai gerar o instalador .msi mas está faltando agora adicionar as DLLs do MSVCR. Então baixe de acordo com a sua versão do python:

Agora você precisa modificar o código do seu arquivo .iss pra ele instalar as DLLs junto com o seu programa. Isso aqui eu usei essa solução que achei no StackOverFlow e ela funciona muito bem:

Na seção Files coloque o caminho do vc_redist que você baixou:

[Files]
Source: "vcredist_x86.exe"; DestDir: {tmp}; Flags: deleteafterinstall

No final da seção Run adicione esse código:

[Run]
; add the Parameters, WorkingDir and StatusMsg as you wish, just keep here
; the conditional installation Check
Filename: "{tmp}\vcredist_x86.exe"; Check: VCRedistNeedsInstall

[Code]
#IFDEF UNICODE
 #DEFINE AW "W"
#ELSE
 #DEFINE AW "A"
#ENDIF
type
 INSTALLSTATE = Longint;
const
 INSTALLSTATE_INVALIDARG = -2; // An invalid parameter was passed to the function.
 INSTALLSTATE_UNKNOWN = -1; // The product is neither advertised or installed.
 INSTALLSTATE_ADVERTISED = 1; // The product is advertised but not installed.
 INSTALLSTATE_ABSENT = 2; // The product is installed for a different user.
 INSTALLSTATE_DEFAULT = 5; // The product is installed for the current user.

 VC_2005_REDIST_X86 = '{A49F249F-0C91-497F-86DF-B2585E8E76B7}';
 VC_2005_REDIST_X64 = '{6E8E85E8-CE4B-4FF5-91F7-04999C9FAE6A}';
 VC_2005_REDIST_IA64 = '{03ED71EA-F531-4927-AABD-1C31BCE8E187}';
 VC_2005_SP1_REDIST_X86 = '{7299052B-02A4-4627-81F2-1818DA5D550D}';
 VC_2005_SP1_REDIST_X64 = '{071C9B48-7C32-4621-A0AC-3F809523288F}';
 VC_2005_SP1_REDIST_IA64 = '{0F8FB34E-675E-42ED-850B-29D98C2ECE08}';
 VC_2005_SP1_ATL_SEC_UPD_REDIST_X86 = '{837B34E3-7C30-493C-8F6A-2B0F04E2912C}';
 VC_2005_SP1_ATL_SEC_UPD_REDIST_X64 = '{6CE5BAE9-D3CA-4B99-891A-1DC6C118A5FC}';
 VC_2005_SP1_ATL_SEC_UPD_REDIST_IA64 = '{85025851-A784-46D8-950D-05CB3CA43A13}';

 VC_2008_REDIST_X86 = '{FF66E9F6-83E7-3A3E-AF14-8DE9A809A6A4}';
 VC_2008_REDIST_X64 = '{350AA351-21FA-3270-8B7A-835434E766AD}';
 VC_2008_REDIST_IA64 = '{2B547B43-DB50-3139-9EBE-37D419E0F5FA}';
 VC_2008_SP1_REDIST_X86 = '{9A25302D-30C0-39D9-BD6F-21E6EC160475}';
 VC_2008_SP1_REDIST_X64 = '{8220EEFE-38CD-377E-8595-13398D740ACE}';
 VC_2008_SP1_REDIST_IA64 = '{5827ECE1-AEB0-328E-B813-6FC68622C1F9}';
 VC_2008_SP1_ATL_SEC_UPD_REDIST_X86 = '{1F1C2DFC-2D24-3E06-BCB8-725134ADF989}';
 VC_2008_SP1_ATL_SEC_UPD_REDIST_X64 = '{4B6C7001-C7D6-3710-913E-5BC23FCE91E6}';
 VC_2008_SP1_ATL_SEC_UPD_REDIST_IA64 = '{977AD349-C2A8-39DD-9273-285C08987C7B}';
 VC_2008_SP1_MFC_SEC_UPD_REDIST_X86 = '{9BE518E6-ECC6-35A9-88E4-87755C07200F}';
 VC_2008_SP1_MFC_SEC_UPD_REDIST_X64 = '{5FCE6D76-F5DC-37AB-B2B8-22AB8CEDB1D4}';
 VC_2008_SP1_MFC_SEC_UPD_REDIST_IA64 = '{515643D1-4E9E-342F-A75A-D1F16448DC04}';

 VC_2010_REDIST_X86 = '{196BB40D-1578-3D01-B289-BEFC77A11A1E}';
 VC_2010_REDIST_X64 = '{DA5E371C-6333-3D8A-93A4-6FD5B20BCC6E}';
 VC_2010_REDIST_IA64 = '{C1A35166-4301-38E9-BA67-02823AD72A1B}';
 VC_2010_SP1_REDIST_X86 = '{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5}';
 VC_2010_SP1_REDIST_X64 = '{1D8E6291-B0D5-35EC-8441-6616F567A0F7}';
 VC_2010_SP1_REDIST_IA64 = '{88C73C1C-2DE5-3B01-AFB8-B46EF4AB41CD}';

 // Microsoft Visual C++ 2012 x86 Minimum Runtime - 11.0.61030.0 (Update 4) 
 VC_2012_REDIST_MIN_UPD4_X86 = '{BD95A8CD-1D9F-35AD-981A-3E7925026EBB}';
 VC_2012_REDIST_MIN_UPD4_X64 = '{CF2BEA3C-26EA-32F8-AA9B-331F7E34BA97}';
 // Microsoft Visual C++ 2012 x86 Additional Runtime - 11.0.61030.0 (Update 4) 
 VC_2012_REDIST_ADD_UPD4_X86 = '{B175520C-86A2-35A7-8619-86DC379688B9}';
 VC_2012_REDIST_ADD_UPD4_X64 = '{37B8F9C7-03FB-3253-8781-2517C99D7C00}';

function MsiQueryProductState(szProduct: string): INSTALLSTATE; 
 external 'MsiQueryProductState{#AW}@msi.dll stdcall';

function VCVersionInstalled(const ProductID: string): Boolean;
begin
 Result := MsiQueryProductState(ProductID) = INSTALLSTATE_DEFAULT;
end;

function VCRedistNeedsInstall: Boolean;
begin
 // here the Result must be True when you need to install your VCRedist
 // or False when you don't need to, so now it's upon you how you build
 // this statement, the following won't install your VC redist only when
 // the Visual C++ 2010 Redist (x86) and Visual C++ 2010 SP1 Redist(x86)
 // are installed for the current user
 Result := not (VCVersionInstalled(VC_2010_REDIST_X86) and 
 VCVersionInstalled(VC_2010_SP1_REDIST_X86));
end;


Agora salve e compile seu arquivo. Você terá um arquivo setup.exe como saída na pasta Output (ou o nome que você colocou) e ele vai instalar o seu programa da boneca com as DLLs necessárias pra rodar em qualquer Windows.

Conclusão:

Se você acompanhou os 3 posts você conseguiu criar um programa em python com auto-update e distribuí-lo para qualquer computador windows.

Observações Final sobre Code Signing:

Fiz uma pesquisa sobre code signing no ano passado(2014) , pode ser interessante para alguém:

Autoridade Certificadora Link Preço / ano
Global Sign https://www.globalsign.com/code-signing/ 229 usd
Thawte http://www.thawte.com/code-signing/index.html?tid=a_box_buycs 299 usd
Symantec Verisign http://www.symantec.com/code-signing 499 usd
Godaddy Code Signing http://br.godaddy.com/ssl/code-signing-certificate.aspx 479,99 reais / 199.99 usd
Ksoftware (Comodo partner) http://codesigning.ksoftware.net/ 95 usd
Digicert http://www.digicert.com/code-signing/ev-code-signing-compared.htm 223 usd
Obs:
Comparativo interessante: https://www.sslshopper.com/cheap-code-signing-certificates.html

Distribuir Programas em Python para Desktops Windows

Comecei a escrever um post sobre como criar um programa com auto-update em Python e como vi que tinha muito assunto pra falar eu resolvi escrever 3 posts, em cada um vou tratar de um assunto mas o objetivo final é conseguir criar um executável em Python com auto-update e que tenha um instalador.

Então os posts estão divididos em 3 partes:

  1. Como criar um executável com Instalador MSI com Python e Cx_freeze
  2. Como criar um programa com Auto-Update utilizando Python e Esky
  3. Como criar um instalador MSI utilizando o Inno Setup

Durante esses 3 posts vou criar um programa bem simples (chamado boneca), mostrar como eu posso criar o instalador pelo próprio Cx_freeze, depois mostrar como posso criar o auto-update utilizando o Esky e por fim mostrar como posso usar o Inno Setup pra criar um instalador mais robusto e mais personalizado.

No final você poderá distribuir programas em python para desktops windows sem nenhum problema.

Muitas pessoas pensam que o Python serve só para fazer scripts e que não pode ser compilado e shipado. Mas não é bem assim, python também pode ser compilado e você pode distribuir software python sem o código-fonte.

Um grande exemplo disso é o Dropbox. O cliente do Dropbox foi escrito em Python para ser portável para Windows, Mac e Linux e a única diferença é que para Windows e Linux ele roda o wxPython no UI e no Mac ele usa Python-ObjC. As próprias palavras do Guido Van Rossum são muito legais falando sobre isso:

 

“Python plays an important role in Dropbox’s success: the Dropbox client, which runs on Windows, Mac and Linux (!), is written in Python. This is key to the portability: everything except the UI is cross-platform. (The UI uses a Python-ObjC bridge on Mac, and wxPython on the other platforms.) Performance has never been a problem — understanding that a small number of critical pieces were written in C, including a custom memory allocator used for a certain type of objects whose pattern of allocation involves allocating 100,000s of them and then releasing all but a few. Before you jump in to open up the Dropbox distro and learn all about how it works, beware that the source code is not included and the bytecode is obfuscated. Drew’s no fool. And he laughs at the poor competitors who are using Java.”

O texto original dele é esse aqui!

9 Dicas de Como Ganhar Hackathons

Como ganhar hackathons parece um título bastante pretensioso, mas minha intenção é compartilhar o que aprendi nos 8 hackathons que já participei.

A primeira vez que participei foi em 2013 no Google Developer Bus, de lá pra cá acabei gostando da ideia e só no ano passado participei de mais 7 hackathons, 3 deles no Vale do Silício.

Pra quem não sabe, hackathon é uma maratona hacker, maratona de programação ou simplesmente um motivo pra juntar um monte de programadores em um lugar e virar uma ou mais noites programando, comendo pizza e bebendo cerveja.

Desses hackathons que participei fui premiado 3 vezes e é muito legal ganhar, as vezes você ganha dinheiro, as vezes um gadget, mas se for pra participar só pelo prêmio esqueça… Na maioria das vezes o prêmio é menor do que o valor das suas horas trabalhadas. Então o objetivo é a diversão, o networking e o aprendizado.

 

1 – Estude o Hackathon Antes

Existem 2 tipos principais de hackathon: Os voltados para programação e os voltados para startups. Se você souber de antemão em qual tipo de hackathon você está se metendo já poderá poupar muito trabalho e/ou estresse. A maioria dos hackathons mostra quem serão os juízes e de olho neles você já consegue saber com o que eles estão envolvidos.

Hackathons voltados para programação vão exigir mais do aspecto técnico do seu Hack, o demo será muito importante e ele precisa estar funcionando.

Hackathons voltados para startups estão preocupados com o modelo de negócio do seu projeto. Nesse tipo de hackathon é comum apresentações com slides, sem demo, com produtos inacabados e é possível ganhar apenas com uma apresentação. Não adianta se espernear, vários juízes que não são técnicos não sabem avaliar se o que você mostrou está funcionando ou se é uma animação no power point.

Como o Gabriel Pugliese me lembrou, é bom também ficar de olho nas Regras do Hackathon, podem ter regras específicas quanto ao software que você pode utilizar, se você precisa ou não deixar o projeto aberto no Github, etc…

Outro ponto importante são os critérios de avaliação do hackathon. Os critérios podem ter pesos e se o seu objetivo é ganhar, então foque nos critérios.

classificacao

Nem sempre o grau de desenvolvimento é o critério com maior peso. Participei do Samsung Ocean Hackathon no ano passado e esse era um hackathon um pouco diferente, ele teria apenas 11 horas de duração incluindo a apresentação e o julgamento, no final das contas tinha umas 9 horas de duração. Os critérios para julgamento eram Inovação, UX e Desenvolvimento e a maior nota de Inovação chegava a 9320 pontos enquanto a de desenvolvimento chega apenas a 200. É claro que com um número baixo de horas para programar eles não priorizariam o desenvolvimento, porém eu e meu grupo não nos atentamos às notas dos critérios. Escolhemos uma ideia sem muita inovação para podermos desenvolver em um tempo curto e conseguimos a maior nota de desenvolvimento mas ficamos em sétimo lugar. Detalhe: A equipe vencedora ficou com a menor nota de desenvolvimento. Mas os critérios eram claros e estavam escritos, nós não estudamos o projeto antes =/

2 – Tenha um bom Time

Você pode ser um Solo Developer, pode ser o melhor Full-Stack dos sete Mares ou ainda ser o maior jogador de squash do mundo. Em um hackathon você vai precisar de um time.

Até porque na maioria dos hackathons é obrigatório ter um time e é uma experiência muito bacana, vocês se xingam, jogam a culpa um no outro, cantam musiquinhas irritantes, assistem um monte de youtube poops e ainda por cima programam.

Se você puder ir com um time formado, chame pessoas que já trabalham bem com você, que vão se comprometer, segurar a barra quando você não aguentar mais e que seja divertido passar mais de 24 horas acordado ao lado delas.

No primeiro hackathon que fui tivemos um problema com um cara do time. Os times eram escolhidos numa espécie de sorteio. Cada pessoa que era sorteada escolhia uma ideia na parede e quando se juntavam 4 pessoas de áreas diferentes o time estava formado. Eu entrei como back-end developer, tinhamos uma frontend, um gerente de projetos e um designer. O designer era um cara super difícil, não deixou a frontend trabalhar, quis fazer o frontend sozinho, não aceitava nenhuma ideia, criticava tudo e tornou a vida de todo mundo um saco durante quase 3 dias, mas mesmo assim a experiência foi super válida, dezenas de kit-kats, várias amizades e contatos importantes e de quebra eu aprendi a usar o Django non-rel com o AppEngine.

Em quase todas as vezes que o time podia ir montado eu fui com o Roberto Civille, o cara já ganhou tantos hackathons que o pessoal já está chamando de Serial Hackathon Winner.

3 – Know Your Shit

Você não pode chegar completamente despreparado para um hackathon. Há espaço para aprender muitas coisas, mas não chegue com um ambiente recém formatado sem sua IDE preferida, sem os programas que você vai usar e sem saber como instalar coisas novas. O tempo é seu inimigo durante o hackathon. Se você perder uma hora pra configurar alguma coisa ou instalar o Phonegap no windows pode acabar sendo prejudicado e vai acabar irritando o pessoal do seu grupo.

4 – Tenha uma boa Ideia

Fácil falar, difícil de colocar em prática. Depois que você começa a frequentar hackathons você percebe que algumas ideias SEMPRE se repetem, principalmente nos hackathons voltados para startups. Tem a WishList, tem o Guia de Baladas, Compartilhamento de Eventos entre amigos, etc… Depois de alguns hackathons você vai ver que as ideias se repetem e vai ver que você mesmo tem essas ideias, elas são simples, são as primeiras ideias que vem à cabeça. Pensar na ideia não é perder tempo. Gaste o tempo pensando e discutindo a ideia, estude as APIs dos patrocinadores, quais terão que ser utilizadas, veja qual tipo de integração seria legal que existisse entre elas. Se for uma coisa útil, ótimo. Se não for, que seja divertida!

No começo de setembro eu participei do Techcrunch Disrupt Hackathon e vencemos na categoria Melhor uso da API do Concur. A ideia partiu do Roberto, que queria ser lembrado de lugares que ele já tinha visitado em San Francisco. A partir dessa ideia, resolvemos desenvolver o mínimo que daria para provar o conceito em apenas 24 horas. Como eu tenho experiência com softwares e APIs de viagens como Sabre, Expedia e Regente eu acabei ficando com a API do Concur e o Roberto com a API do Evernote. Fizemos o BizMem um aplicativo que juntava todas as notas que foram criadas pelo usuário quando ele estava viajando e juntava com as informações de viagem fornecidas pela Concur, assim poderíamos melhorar a experiência de Viajantes à negócios. O pessoal do Evernote e do Concur gostou muito.

5 – Corte Requisitos

Não tente fazer tudo. Nos hackathons o legal é desenvolver coisas novas e não perder tempo com aquilo que todo mundo sabe. Se o login não for tão importante para o core do que você quer mostrar então corte o login. Se você não é viciado em testes, corte os testes. Corte o monte de ideias que vocês tiveram e deixe só o que dá pra fazer. Conforme o desenvolvimento for se desenrolando fique atento ao relógio, se estiver demorando demais em uma tarefa, veja se você não vai ter que compensar mais na frente ou se você terá que cortar aquela tarefa.

6 – Faça Perguntas

Como todo bom programador você deve estar acostumado a procurar pelas respostas sozinho. Ler documentações, perguntar no StackOverflow, abrir Códigos Fonte e etc. O problema é que as vezes a resposta para o que você busca não está tão fácil de achar e na maioria das vezes existe um developer advocate da API do patrocinador pronto para te ajudar durante o hackathon. Perguntar para esse cara é sempre uma boa ideia, primeiro porque ele tem mais experiência com aquela API do que você, segundo porque é uma forma de mostrar pra ele (que comumente é um Juiz) o que você está fazendo. Esses caras podem contribuir com dicas muito úteis sobre a sua ideia e sobre o desenvolvimento, não desperdice esta oportunidade!

Além disso, pergunte para outros participantes, converse com eles, vá tomar café, coma junto com eles, tente se entrosar, afinal todo mundo ali tem pelo menos alguma coisa em comum com você, assunto é o que não vai faltar

7 – Make it Work, Bitches!

Faça seu demo funcionar. Parece idiota, mas por incrível que pareça, em todos os hackathons que eu fui vi pessoas apresentando algo que não funcionava, ou porque viajaram demais na ideia, ou não sabiam usar a tecnologia que escolheram ou não souberam manejar o tempo cortando requisitos iniciais.

Só não vai queimar o fusível aí, o networking é muito importante. Trabalhe de forma inteligente pra fazer funcionar mas aproveite pra curtir a oportunidade de estar com outras pessoas.

8 – Spread the Word!

Teve a sua ideia e está conseguindo desenvolver? Comece a falar sobre ela! Mostre para os outros grupos, mostre para os patrocinadores, mostre para os juízes, o importante é você aproveitar a maior contribuição que eles podem te dar que é o Feedback. As apresentações geralmente são muito curtas e o seu produto pode não ser tão bem explicado naqueles poucos minutos que você vai falar, ou talvez você não apresente bem em público, então é melhor já mostrar seu produto pra todo mundo antes da apresentação. Se possível monte uma marca, faça as pessoas lembrarem do que você fez.

Em março de 2014 eu participei do API HackDay que era promovido pelo Twitter e pelo SendGrid. Nós inventamos um produto que chamamos de Vai Bilu, a ideia era poder fazer coisas pela internet usando apenas o email ou o twitter, porque alguns planos de celular dão acesso irrestrito ao Twitter ou ao seu E-mail. Fizemos em homenagem ao ET Bilu e ficamos o final de semana todo falando com a voz aguda do ET. No final das contas, ganhamos em primeiro lugar no Hackathon e todo mundo sabia o que era o Vai Bilu, estavam imitando o ET Bilu também e se divertindo com a gente. Virou uma marca! A fórmula deu tão certo que resolvemos homenagear o ET Bilu em mais duas ocasiões, no Angel Hack com o Toca Bilu (um brinquedo feito com Arduino) e no VemBilu uma espécie de Tinder para Estudar que ganhou em primeiro lugar na categoria Educação no Hackathon da CJE – FIESP

9 – Participe!

Quando o Andrés Sanches assumiu o Corinthians ele foi perguntado como ia fazer pra que o Corinthians ganhasse uma Libertadores e a resposta dele foi: Primeiro temos que participar mais! Depois disso o Corinthians se classificou várias vezes seguidas para a Libertadores e foi Campeão da Copa Libertadores da América de 2012.

Para ganhar hackathons você precisa fazer a mesma coisa: Participar! Óbvio que você não vai ganhar prêmio em todos, mas com certeza você vai ganhar outras coisas como conhecimento, networking, Freelas e fazer amigos.

Você pode ficar por dentro dos Hackathons nas comunidades do facebook como a Hackathons Brasil e dar uma olhada no Challenge Post

Bônus – Não se misture com a gentalha

Muitas pessoas querem só se aproveitar, principalmente dos mais inocentes. São pessoas que vão em um hackathon com uma Ideia já pronta, não sabem programar mas querem desenvolver um MVP de graça. Fique atento com essas pessoas, na maioria das vezes elas não querem que você contribua com ideias, querem só sua força de trabalho.

Já vi isso acontecer várias vezes e na maioria das vezes essas pessoas conseguem o que querem prometendo parcerias e etc. Tome cuidado! Vi isso até nos Estados Unidos no DataWeek + API World Hackathon. Nesse evento duas pessoas vieram com ideias e falando que não sabiam programar, como eu já tinha visto isso acontecer, disse que não tinha interesse e vi que essas pessoas passaram por todas as mesas antes de irem embora.

Bônus – A apresentação

Na hora de apresentar mostre o seu demo funcionando. Deixe que as pessoas o testem. Responda as perguntas que foram feitas antes, quando você recebeu o feedback dos outros participantes do hackathon. Fale das APIs que você usou, mostre o que aprendeu e faça a apresentação ser legal, afinal todo mundo está cansado de ficar ali.

O principal na hora da apresentação: Nunca confie na Internet. Pode ser que não tenha na hora da apresentação e dê tudo errado, tenha o que você precisar rodando localmente também, se der algum problema você mostra a versão local e sem crise.

Fizemos um hack muito legal, o NewsMood.me, que mostrava o humor das notícias relacionadas a um termo e na hora da apresentação a internet falhou, ou seja, não deu pra apresentar =(