Browse Source

Merge branch 'release/20200417'

master
Julio Biason 5 years ago
parent
commit
649b6b5ef7
  1. 75
      content/code/microservices-chassis.md
  2. 88
      content/code/microservices-chassis.pt.md
  3. 273
      content/code/you-dont-need-range.md
  4. 283
      content/code/you-dont-need-range.pt.md

75
content/code/microservices-chassis.md

@ -0,0 +1,75 @@
+++
title = "Microservices: Chassis"
date = 2020-04-17
[taxonomies]
tags = ["microservices", "chassis", "frameworks", "languages"]
+++
The chassis for a microservices fleet is defined as the libraries and
frameworks that one should use when creating a new microservice.
<!-- more -->
The "chassis" is actually a [known
pattern](https://microservices.io/patterns/microservice-chassis.html), but the
literature points it as a decision about the libraries and frameworks that
should be used when creating microservices.
For example, if you're working with Java, you'd probably pick something like
Spring Boot as a chassis for your microservices, in a way that anyone creating
a new microservice already have the library (and local knowledge) on how to
build it.
And, for each language, you need to pick a different chassis -- you can't use
Spring Boot with Python, for example.
You may have noticed that "but" in the second paragraph. Personally, I think
the choice of chassis go way beyond just creating a microservices.
## Shared Knowledge
One of the major factors of using chassis for your microservices is the shared
knowledge between teams. Teams that are using the same chassis can exchange
solutions on how to solve some problems, how to make processing faster, new
releases information and so on.
Even if the teams will never touch each other's code, the simply fact that
they can share these information between them is a huge boom.
And even for teams using different languages is a major point: One team can
point that their framework allows them to do things in a more simpler way that
can be researched by some other team, using a complete different framework, in
a complete different language, on how to build the same stuff.
## Applying a Common View
While the pattern describes only frameworks and libraries, the choices of
surrounding services also makes part of the chassis, in my opinion.
For example, the teams pick Kafka as a messaging broker between services --
which would allow any team, on any framework, on any language, to use the same
service for exchanging messages -- allowing any team that has a need
for a message broker can use the same install (but using different
topics) and reduce maintenance costs. But what happens when one team decides
to use Kafka as a database and put a retention to "forever"? That would
utterly confused everyone else. "Why is this topic getting bigger and
bigger?" Worse, without a well described DevOps documentation, someone may see
that growing topic, check it out, see that is has no retention policy and add
one based on the other projects.
Another example: For highly relational data, there is a PostgreSQL
installation for everyone. Each team have their own database and users. But
one team, which got responsible for two microservices, have one with data
which is relational and another that basically requires a key-value store.
Instead of asking for key-value store, they decide to create a database with a
single table, with a key field and a large text field for storing JSONs.
This, again, breaks the defined chassis, as one would expect PostgreSQL to be
the relational database and not as a key-value store.
## Conclusion
Chassis are good for microservices development due their quick development
start up and shared knowledge, but they go way beyond just frameworks and
libraries: They related to everything around the services and the way they are
viewed by each microservice.

88
content/code/microservices-chassis.pt.md

@ -0,0 +1,88 @@
+++
title = "Microserviços: Chassi"
date = 2020-04-17
[taxonomies]
tags = ["microserviços", "chassi", "framework", "linguagens"]
+++
O chassi de um fleet de microserviços é definido como as bibliotecas e
frameworks que alguém deve usar quando está criando um novo microserviço.
<!-- more -->
O "chassi" é um [design
pattern](https://microservices.io/patterns/microservice-chassis.html)
conhecido, mas a literatura fala sobre a escolha de bibliotecas e frameworks
que devem ser usados quando se está criando um microserviço.
Por exemplo, se você está trabalhando com Java, você provavelmente teria algo
como Spring Boot como chassi para os seus microserviços, de forma que qualquer
um que comece um microserviço já tenha uma biblioteca (e conhecimento local)
de como construir.
E, para cada linguagem, você precisa escolher um chassi diferente -- você não
pode usar Spring Boot com Python, por exemplo.
Você deve ter notado que eu coloquei um "mas" no segundo parágrafo.
Pessoalmente, eu acho que a escolha do chassi vai bem além da criação de
microserviços.
## Conhecimento Compartilhado
Um dos maiores fatores de se usar um chassi para o seu microserviço é o
conhecimento compartilhado entre os times. Times que utilizam o mesmo chassi
podem trocar informações em como solucionar alguns problemas, como fazer o
processamento ficar mais rápido, informações sobre releases novas e assim por
diante.
Mesmo que os times nunca mexam nos códigos dos outros, o simples fato que eles
podem compartilhar essas informações entre eles é um grande avanço.
E mesmo para times que utilizam linguagens diferentes isso é um grande ponto:
Um time pode descrever como o framework que eles utilizam permite fazer alguma
coisa de forma mais simples, de forma que outro time possa pesquisar se o seu
chassi permite fazer algo da mesma forma.
## Aplicando uma Visão Comum
Enquanto o pattern descreve apenas frameworks e bibliotecas, as escolhas dos
serviços ao redor do serviço também faz parte do chassi, na minha opinião.
Por exemplo, um time decide usar Kafka como mensageria[^1] entre serviços -- o
que permite que qualquer outro time, usando qualquer outro framework, em
qualquer outra linguagem, a usar o mesmo serviço para troca de mensagens --
permitindo que qualquer time que precise usar um serviço de troca de mensagens
possa usar a mesma instalação (mas usando tópicos diferentes), reduzindo
custos de manutenção. Mas o que acontece quando um time decide usar Kafka como
banco de dados e define o tempo de retenção para "sempre"? Isso iria confundir
completamente todos os outros. "Por que esse tópico está sempre crescendo?"
Pior, sem uma documentação de DevOps bem descrita, alguém pode ver o tópico
crescendo, verificar, ver que a política de retenção está diferente do resto e
adicionar um baseada em outros projetos.
Outro exemplo: Para dados relacionados, há uma instalação do PostgreSQL para
todo mundo. Cada time tem o seu próprio database e usuários. Mas um time, que
acabou ficando responsável por dois microserviços, tem um serviço com dados
que são relacionais e outro que basicamente necessita de um armazenamento de
chave-valor. Ao invés de pedir por um banco de dados chave-valor, o time
decide criar um database com apenas uma tabela, com um campo para a chave e um
campo de texto para guardar JSONs. Isso, novamente, quebra a definição do
chassi, pois seria esperado que o PostgreSQL fosse usado como banco
relacional e não como um banco chave-valor.
## Conclusão
Chassis são bons para o desenvolvimento de microserviços por permitir um
início de desenvolvimento rápido e com compartilhamento de conhecimento, mas
eles vão além de apenas frameworks e bibliotecas: eles relacionado tudo ao
redor dos serviços e como esses são vistos por cada microserviço.
---
[^1]: "Message broker".
<!--
vim:spelllang=pt:
-->

273
content/code/you-dont-need-range.md

@ -0,0 +1,273 @@
+++
title = "You Don't Need range()"
date = 2020-04-16
[taxonomies]
tags = ["code", "python", "range"]
+++
Beginners in Python tend to use `range()` for iterating over lists. This is
not really necessary.
<!-- more -->
When people start programming Python, they tend to use constructions coming
from other languages, so they iterate over a list with something like:
```python
a_list = [1, 2, 3, 4]
for i in range(len(a_list)):
print(a_list[i])
```
But Python have the concept of "iterable", meaning some things can be iterated
over, without the need of accessing each element individually. For example,
our previous list can be iterated with:
```python
a_list = [1, 2, 3, 4]
for value in a_list:
print(value)
```
"For every element in `a_list`, retrieve it and name it `value`."
A lot of elements are iterable: Strings are iterable, returning every
character in them; dictionaries are iterable, returning every key in them;
sets are iterable, returning every element in them; tuples are iterable,
returning every value in them; generators are iterable, return the next value
they can produce.
But what if you need to iterate over more than one iterable at the same time?
## Enters `zip()`
That's where `zip()` comes in. `zip()` allows you to merge iterables:
```python
a_list = [1, 2, 3, 4]
a_tuple = ('a', 'b', 'c', 'd')
for mixed_tuple in zip(a_list, a_tuple):
print(mixed_tuple)
```
This code prints out:
```
(1, 'a')
(2, 'b')
(3, 'c')
(4, 'd')
```
What `zip()` does is create a tuple with the first element of the first
iterable and the first element of the second iterable; then the second element
of the first iterable and the second element of the second iterable; and so
on. You can put as many iterables as you want in `zip()` and it will just
create larger tuples for each interaction.
## Interlude: Destructuring
One of the cool things in Python is "destructuring". Destructuring
(de-structuring or more like "breaking apart a structure") allows one to
extract elements from a iterable directly.
For example, if you have a tuple with two elements:
```python
a_tuple = (1, 2)
```
... you'd probably take every element of it in separate variables with
something like
```python
a = a_tuple[0]
b = a_tuple[1]
```
But with destructuring, you can do this in a single pass with
```python
(a, b) = a_tuple
```
This code and the one above it will do exactly the same thing.
But why destructuring is important if we are talking about iterating over
elements? 'Cause `for` also has the destructuring capabilities:
```python
a_list = [1, 2, 3, 4]
a_tuple = ('b', 'c', 'd', 'f')
a_string = 'aeio'
for (a_number, lowercase_char, uppercase_char) in zip(a_list, a_tuple, a_string):
print(a_number)
print(lowercase_char)
print(uppercase_char)
print()
```
{% note() %}
Remember that I said that strings are also iterables and each iteration would
return a character? That's it.
{% end %}
But what happens when one of the iterables is smaller than the other one?
```python
a_short_list = [1, 2]
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90]
for (small, big) in zip(a_short_list, a_long_list):
print(small, big)
```
That will print
```
1 10
2 20
```
`zip()` stops when the shortest iterable have no more elements. To go as far
as the longest iterable, you need `itertools.zip_longest()`.
## `itertools.zip_longest()`
`zip_longest()`, part of the `itertools` module, will transverse the iterables
till every one of them have no more elements. What happens with the shortest
of those is that its value will be replaced with `None`. Using our previous
example:
```python
import itertools
a_short_list = [1, 2]
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90]
for (small, big) in itertools.zip_longest(a_short_list, a_long_list):
print(small, big)
```
That will print:
```
1 10
2 20
None 30
None 40
None 50
None 60
None 70
None 80
None 90
```
## Careful with generators
One thing you must be careful when using `zip()` and `zip_longest()` are
generators. Why? Because some of them have no end.
Let's take one example: `cycle()`. `cycle()`, also part of the itertools
module, is a generator that, on request, returns the next element of an
iterable but, as soon as this iterable is over, it starts over. For example
(and I'm tacking `zip()` around this just for the sake of staying on topic,
and you don't need to use `zip()` with `cycle()`):
```python
a_list = [10, 20, 30, 40, 50, 60, 70, 80, 90]
for (bullet, value) in zip(cycle(['-', '*', '.']), a_list):
print(bullet, value)
```
That will produce:
```
- 10
* 20
. 30
- 40
* 50
. 60
- 70
* 80
. 90
```
What happened here is that `zip()` took the first value of the first iterable,
our `cycle(['-', '*', '.'])`, which was the first value of its iterable,
`'-'`, and the second value of the second iterable, `10`; next iteration, the
second value of `cycle()` was `'*'` and the second value of `a_list` was `20`;
third iteration, `cycle()` returned `'.'` and `a_list` returned `30`; now, on
the fourth iteration, `cycle()` was asked for a value and, with its iterable
exhausted, it returned to the first value, returning `'-'` again.
Ok, cool?
So, what's the problem with generators?
Some generators -- like `cycle()` above -- do not have an end. If you replace
`zip()` with `zip_longest()` on the code above, you'll see that the code will
never stop. It's not every generator the can produce values continuously,
though, so you can mess with them with no issue.
{% note() %}
It's not `zip_longest()` that may have an issue. You can put two `cycle()`s in
a `zip()` and it will keep producing tuples with no end.
{% end %}
All nice and dandy, but what if I need to show the index itself?
## `enumerate()` to the rescue!
Ok, so we talked about mixing more than one iterable, but what if we need the
position? What if we have a list of ordered results and we need to show the
position itself?
Again, you may be temped to use `range()`:
```python
winners = ['first place', 'second place', 'third place', 'fourth place']
for pos in range(len(winners)):
print(pos + 1, winners[pos].capitalize())
```
That will print:
```
1 First place
2 Second place
3 Third place
4 Fourth place
```
One may also try to be clever and mix our newly found knowledge about `zip()`
and do:
```python
winners = ['first place', 'second place', 'third place', 'fourth place']
for (pos, name) in zip(range(len(winners)), winners):
print(pos + 1, name.capitalize())
```
... which ,personally, looks even more cumbersome than the first option. But
Python have another generator called `enumerate()` that takes one single
iterable, but produces tuples with the index of it and its value:
```python
winners = ['first place', 'second place', 'third place', 'fourth place']
for (pos, name) in enumerate(winners):
print(pos + 1, name.capitalize())
```
Even better, `enumerate()` have an option to define with will be the value of
the first element, so instead of that `pos + 1` in the `print()` statement, we
can replace the enumerate to `enumerate(winners, start=1)` and remove the
addition in `print()`.
## Conclusion
Iterables is one of the powerhouses of Python, as you may have noticed in the
beginning with the number of things that can be iterated over. Understanding
those will help you write better and more concise Python code, without losing
meaning.

283
content/code/you-dont-need-range.pt.md

@ -0,0 +1,283 @@
+++
title = "Você Não Precisa de range()"
date = 2020-04-16
[taxonomies]
tags = ["código", "python", "range"]
+++
Quem está começando com Python tende a usar `range()` quando precisa iterar
sobre listas. Mas isso não é realmente necessário.
<!-- more -->
Quando as pessoas começam a programar em Python, elas tendem a usar
construções vindas de outras linguagens, e por isso iteram sobre uma lista da
seguinte forma:
```python
a_list = [1, 2, 3, 4]
for i in range(len(a_list)):
print(a_list[i])
```
Mas Python tem o conceito de "iteráveis", o que quer dizer que algumas coisas
podem ser iteradas diretamente, sem precisar acessar cada elemento
individualmente. Por exemplo, nossa lista anterior poderia ser iterada com:
```python
a_list = [1, 2, 3, 4]
for value in a_list:
print(value)
```
"Para cada elemento em `a_list`, recupere-o e chame-o de `value`."
Vários elementos são iteráveis: Strings são iteráveis, retornando cada
caractere nelas; dicionários são iteráveis, retornado cada chave neles;
conjuntos são iteráveis, retornado cada elemento neles; tuplas são iteráveis,
retornando cada elemento nelas; generators são iteráveis, retornando o próximo
valor que eles conseguem produzir.
Mas e se precisássemos iterar sobre mais de um elemento ao mesmo tempo?
## Entra o `zip()`
É aí que o `zip()` entra. `zip()` permite que você junte dois iteráveis:
```python
a_list = [1, 2, 3, 4]
a_tuple = ('a', 'b', 'c', 'd')
for mixed_tuple in zip(a_list, a_tuple):
print(mixed_tuple)
```
Esse código imprime:
```
(1, 'a')
(2, 'b')
(3, 'c')
(4, 'd')
```
O que o `zip()` faz é criar uma tupla com o primeiro elemento do primeiro
iterável e o primeiro elemento do segundo iterável; depois com o segundo
elemento do primeiro iterável e o segundo elemento do segundo iterável; e
assim por diante. Você pode colocar quantos iteráveis você quiser no `zip()` e
ele ira produzir tuplas maiores em cada iteração.
## Interlúdio: Destruturação
Uma das coisas legais de Python é "destruturação". Destruturação
(de-estruturar ou mais como "quebrar uma estrutura") permite que elementos de
um iterável sejam extraídos diretamente.
Por exemplo, se você tem uma tupla com dois elementos:
```python
a_tuple = (1, 2)
```
... você provavelmente iria extrair cada um dos elementos com alguma coisa do
tipo:
```python
a = a_tuple[0]
b = a_tuple[1]
```
Mas com destruturação, você pode fazer isso numa única passada com:
```python
(a, b) = a_tuple
```
Este código e o acima dele fazem exatamente a mesma coisa.
Mas porque destruturação é importante se estamos falando sobre iterar sobre
elementos? Porque `for` também tem a capacidade de destruturar:
```python
a_list = [1, 2, 3, 4]
a_tuple = ('b', 'c', 'd', 'f')
a_string = 'aeio'
for (a_number, lowercase_char, uppercase_char) in zip(a_list, a_tuple, a_string):
print(a_number)
print(lowercase_char)
print(uppercase_char)
print()
```
{% note() %}
Lembra que eu falei que strings também eram iteráveis e cada iteração traz um
caractere? É isso.
{% end %}
Mas o que acontece quando um dos iteráveis é menor que o outro?
```python
a_short_list = [1, 2]
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90]
for (small, big) in zip(a_short_list, a_long_list):
print(small, big)
```
Esse código imprime:
```
1 10
2 20
```
`zip()` pára quando o menor iterável não tem mais elementos. Para consumir
todos os elementos do iterável mais longo, você precisa de
`itertools.zip_longest()`.
## `itertools.zip_longest()`
`zip_longest()`, parte do módulo `itertools`, irá percorrer os iteráveis até
que nenhum deles tenha mais elementos. O que acontece com o menor deles é que
os seus valores são substituídos por `None`. Usando nosso exemplo anterior:
```python
import itertools
a_short_list = [1, 2]
a_long_list [10, 20, 30, 40, 50, 60, 70, 80, 90]
for (small, big) in itertools.zip_longest(a_short_list, a_long_list):
print(small, big)
```
Isso irá imprimir:
```
1 10
2 20
None 30
None 40
None 50
None 60
None 70
None 80
None 90
```
## Cuidado com generators
Uma coisa que você precisa ter cuidado quando estiver usando `zip()` ou
`zip_longest()` são generators. Por que? Porque alguns deles não tem fim.
Vamos usar um exemplo: `cycle()`. `cycle()`, também parte do módulo itertools,
é um generator que, quando for pedido um valor, retorna o próximo valor de um
iterável mas, quando chegar ao fim deste, retorna pro começo. Por exemplo (e
eu estou usando `zip()` apenas para nos mantermos no tópico, mas não é preciso
usar `zip()` para usar `cycle()`):
```python
a_list = [10, 20, 30, 40, 50, 60, 70, 80, 90]
for (bullet, value) in zip(cycle(['-', '*', '.']), a_list):
print(bullet, value)
```
Este código produz:
```
- 10
* 20
. 30
- 40
* 50
. 60
- 70
* 80
. 90
```
O que acontece é que `zip()` pegou o primeiro elemento do primeiro iterável,
nosso `cycle(['-', '*', '.'])`, que tem como primeiro valor no seu iterável
`'-'` e o segundo valor do segundo iterável, `10`; na próxima iteração, o
segundo valor de `cycle()` foi `'*'` e o segundo valor de `a_list` foi `20`;
na terceira iteração, `cycle()` retornou `'.'` e `a_list` `30`; agora, na
quarta iteração, foi pedido um valor ao `cycle()` e, como o seu iterável
terminou, ele retorno o primeiro valor, retornando `'-'` de novo.
Certo?
Então qual o problema com generators?
Alguns generators -- como o `cycle()` acima -- não tem fim. Se você trocar
`zip()` por `zip_longest()` no exemplo acima, você vai ver que o código não
irá terminar. Não são todos os generators que produzem valores de forma
infinita, e você pode usá-los sem problema.
{% note() %}
Não é só `zip_longest()` que tem problemas. Você pode botar dois `cycle()`s
num `zip()` e ele vai ficar gerando tuplas sem parar.
{% end %}
Certo, legal, mas e se eu precisar mostrar o índice também?
## `enumerate()` ao resgate!
Então, nós falamos sobre usar dois iteráveis ao mesmo tempo, mas e se
precisarmos da posição também? E se a nossa lista for uma lista de resultados
ordenados e nós precisamos mostrar a posição em si?
De novo, você pode ficar tentado a usar `range()`:
```python
winners = ['first place', 'second place', 'third place', 'fourth place']
for pos in range(len(winners)):
print(pos + 1, winners[pos].capitalize())
```
Isso irá imprimir:
```
1 First place
2 Second place
3 Third place
4 Fourth place
```
Uma das coisas que você pode tentar ser esperto é tentar misturar o seu novo
conhecimento sobre `zip()` e fazer:
```python
winners = ['first place', 'second place', 'third place', 'fourth place']
for (pos, name) in zip(range(len(winners)), winners):
print(pos + 1, name.capitalize())
```
... que, pessoalmente, parece mais complexo do que a primeira opção. Mas
Python tem outro generator chamado `enumerate()` que recebe um único iterável,
mas produz tuplas com o índice e seu valor:
```python
winners = ['first place', 'second place', 'third place', 'fourth place']
for (pos, name) in enumerate(winners):
print(pos + 1, name.capitalize())
```
Melhor ainda, `enumerate()` tem uma opção para definir o valor inicial do
primeiro elemento, e ao invés de usar `pos + 1` no `print()`, nós podemos
mudar o enumerate para `enumerate(winners, start=1)` e remover a adição no
`print()`.
## Conclusão
Iteráveis são as grandes potências de Python, como você pode ter percebido com
a lista de coisas que podem ser iteradas. Entendendo-os vai lhe ajudar a
escrever código Python melhor e mais conciso, sem perda de significado.
---
Esse conteúdo foi criado baseado nas discussões no [Telegram do
PyTche](https://t.me/pytche). Se quiser, junte-se a nós para conversarmos
sobre Python.
<!--
vim:spelllang=pt:
-->
Loading…
Cancel
Save