Tag: stackoverflow

Porque não acompanho mais o Stack Overflow em Português

sopt

Aviso: o título deste artigo é totalmente sensacionalista.

Do início até a formatura

Em 12 de dezembro de 2013, há exatamente dois anos, nascia o Stack Overflow em Português. Para quem não sabe, trata-se um site de perguntas e respostas para programadores profissionais e entusiastas.

Já escrevi sobre como a participação nesse site me ajudou em vários aspectos, por exemplo: confrontar meu conhecimento com outros bons profissionais, treinar habilidades de análise de problemas, organizar as ideias e escrever de forma clara, aprender diversificadas abordagens para um mesmo problema, acompanhar as tecnologias que estão sendo mais usadas. Leia mais em Resultados de um experimento social.

Leia mais

Problemas com “merge” no JPA?

java_ee

Muitos desenvolvedores enfrentam grandes dificuldades em trabalhar com JPA (Java Persistence API). Vejo que a maioria dessas dificuldades tem raiz na falta de compreensão de certos detalhes do funcionamento do JPA.

Analisarei neste artigo um caso que gera muita confusão, que é o uso do método merge().

Leia mais

Nuances sobre serialização de objetos com herança em Java

serialization

A serialização de objetos em Java é um recurso muito importante e útil em aplicações específicas. Diversas APIs do Java beneficiam-se dela, por exemplo, para chamadas de métodos remotos (RMI) e migração de sessões em Servlets de aplicações web clusterizadas.

Serializar um objeto consiste em converter os valores de uma instância em uma sequência (stream) de bytes (dados binários) de forma que o estado do objeto possa ser posteriormente recuperado.

Tornar uma classe serializável em Java é muito simples: basta implementar a interface java.io.Serializable.

Porém, nem sempre os detalhes são tão óbvios. E quanto a atributos herdados? O que ocorre se a superclasse não for serializável?

Tratando-se de herança, existem algumas nuances quanto ao que será ou não incluído na serialização. O ObjectOutputStream irá serializar todas as classes da hierarquia que são marcados com java.io.Serializable e seus descendentes. Desses, os atributos não estáticos, não transientes e que também são marcados com a referida interface serão serializados.

Meio complicado, não? Vamos ver um…

Exemplo prático (com erro)

Primeiro, duas classes que serão referenciadas, uma serializável e outra não:

class OutraClasseSerializavel implements Serializable {
    int outroValorSerializavel;
}

class OutraClasse {
    int outroValor;
}

Segundo, uma classe “pai” e uma “filha”:

class Pai {
    OutraClasse outraClassePai;
    OutraClasseSerializavel outraClasseSerializavelPai;
    int valorPai;
}

class Filha extends Pai implements Serializable {
    OutraClasse outraClasseFilha;
    OutraClasseSerializavel outraClasseSerializavelFilha;        
    int valorFilha;
}

Note que as duas classes possuem valores e referências para classes serializáveis e não serializáveis.

O que acontece se tentarmos serializar a classe Filha? Ocorre um java.io.NotSerializableException por causa da referência à classe não serializável OutraClasse na classe Filha.

Exemplo prático

Se removermos a referência à classe não serializável da classe Filha, o erro não ocorre:

class Filha extends Pai implements Serializable {
    OutraClasseSerializavel outraClasseSerializavelFilha;        
    int valorFilha;
}

Testando e analisando o resultado

Vamos fazer um teste:

Filha filha = new Filha();

//valores da classe filha
filha.valorFilha = 11;
filha.outraClasseSerializavelFilha = new OutraClasseSerializavel();
filha.outraClasseSerializavelFilha.outroValorSerializavel = 33;

//valores da classe pai
filha.valorPai = 22;
filha.outraClasseSerializavelPai = new OutraClasseSerializavel();
filha.outraClasseSerializavelPai.outroValorSerializavel = 44;
filha.outraClassePai = new OutraClasse();
filha.outraClassePai.outroValor = 55;

//serializa
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("filha.out")));
oos.writeObject(filha);
oos.close();

//recupera classe serializada
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("filha.out")));
Filha filhaRecuperada = (Filha) ois.readObject();
ois.close();

Finalmente, vamos imprimir e analisar os valores retornados…

Atributo primitivo na classe serializável

System.out.println(filhaRecuperada.valorFilha);

Saída:

11

Obviamente, o atributo valorFilha é devidamente serializado e recuperado porque faz parte da classe serializável e é um tipo primitivo.

Referência à classe serializável em uma classe também serializável

System.out.println(filhaRecuperada.outraClasseSerializavelFilha.outroValorSerializavel);

Saída:

33

O atributo outraClasseSerializavelFilha também foi serializado corretamente, assim como seu valor, porque é uma referência a uma classe serializável a partir da classe Filha que é serializável.

Atributo primitivo na classe Pai, que não é serializável

System.out.println(filhaRecuperada.valorPai);

Saída:

0

Observamos agora que, embora não ocorram erros, atributos estáticos em uma superclasse não serializável não são serializados.

Referência à classes serializáveis e não serializáveis em uma superclasse não serializável

System.out.println(filhaRecuperada.outraClassePai);
System.out.println(filhaRecuperada.outraClasseSerializavelPai);

Saída:

null

null

E, finalmente, observamos que referências a classes de qualquer tipo (serializáveis ou não) em uma superclasse não serializável também serão excluídas da serialização.

Considerações

Estender uma classe para torná-la serializável não funciona, pois como foi visto o processo de serialização ignora as superclasses não serializáveis e um erro ocorre ao incluirmos um atributo não serializável.

Mas existe alguma solução? A resposta é sim!

Solução: readObject e writeObject

A documentação da classe java.io.Serializable aponta alguns métodos que devem ser implementados para que você possa alterar “manualmente” a forma como o Java serializa e desserializa um objeto.

As assinaturas são:

private void writeObject(java.io.ObjectOutputStream out)
    throws IOException
private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException;

Exemplo de implementação

Segue uma implementação básica dos métodos readObject() e writeObject() na classe Filha que resolvem o problema da serialização tanto do atributo inteiro da superclasse quanto das referências a outros objetos:

class Filha extends Pai implements Serializable {

    int valorFilha;
    transient OutraClasse outraClasseFilha;
    OutraClasseSerializavel outraClasseSerializavelFilha;

    private void readObject(java.io.ObjectInputStream stream)
            throws IOException, ClassNotFoundException {
        valorFilha =  stream.readInt();
        outraClasseFilha = new OutraClasse();
        outraClasseFilha.outroValor = stream.readInt();
        outraClasseSerializavelFilha = (OutraClasseSerializavel) stream.readObject();

        valorPai = stream.readInt();
        outraClassePai = new OutraClasse();
        outraClassePai.outroValor = stream.readInt();
        outraClasseSerializavelPai = (OutraClasseSerializavel) stream.readObject();
    }

    private void writeObject(java.io.ObjectOutputStream stream)
            throws IOException {
        stream.writeInt(valorFilha);
        stream.writeInt(outraClasseFilha.outroValor);
        stream.writeObject(outraClasseSerializavelFilha);

        stream.writeInt(valorPai);
        stream.writeInt(outraClassePai.outroValor);
        stream.writeObject(outraClasseSerializavelPai);
    }

}

Então fazemos um novo teste:

Filha filha = new Filha();

//valores da classe filha
filha.valorFilha = 11;
filha.outraClasseSerializavelFilha = new OutraClasseSerializavel();
filha.outraClasseSerializavelFilha.outroValorSerializavel = 22;
filha.outraClasseFilha = new OutraClasse();
filha.outraClasseFilha.outroValor = 33;

//valores da classe pai
filha.valorPai = 44;
filha.outraClasseSerializavelPai = new OutraClasseSerializavel();
filha.outraClasseSerializavelPai.outroValorSerializavel = 55;
filha.outraClassePai = new OutraClasse();
filha.outraClassePai.outroValor = 66;

//serializa
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("c.out")));
oos.writeObject(filha);
oos.close();

//recupera classe serializada
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("c.out")));
Filha filhaRecuperada = (Filha) ois.readObject();
ois.close();

//valores da classe filha
System.out.println(filhaRecuperada.valorFilha);
System.out.println(filhaRecuperada.outraClasseSerializavelFilha.outroValorSerializavel);
System.out.println(filhaRecuperada.outraClasseFilha.outroValor);

//valores da classe pai
System.out.println(filhaRecuperada.valorPai);
System.out.println(filhaRecuperada.outraClasseSerializavelPai.outroValorSerializavel);
System.out.println(filhaRecuperada.outraClassePai.outroValor);

E obtemos a saída:

11
22
33
44
55
66

Todos os atributos foram salvos!

Conclusão

Embora o Java não resolva toda a questão da serialização automagicamente, ele nos fornece um mecanismo prático e flexível para resolver isso, pois permite controlar completamente como o objeto é salvo e recuperado do arquivo. Por outro lado, isso exige a codificação manual de cada elemento, na ordem correta.

Este artigo é baseado na minha resposta no Stack Overflow em Português

Créditos da imagem do sapo: GridGain Blog

Stack Overflow and TDC 2014

On August 6 (2014), I went to TDC (The Developer’s Conference) in Sao Paulo, Brazil. As one of the Stack Overflow in Portuguese moderators, I was invited by SO team itself, as they sponsored the event.

If you don’t know yet, the most essential site for developers, Stack Overflow, debuted this year its Portuguese edition. The goal is to provide useful and quality information, in its well known Q&A format, for millions of Portuguese-speaking programmers that aren’t very comfortable with English.

Talks

There were some very interesing talks and I highlight What Do Programmers Care About by Joel Spolsky, founder of Stack Overflow and of the acknowledged site joelonsoftware.com.

He spoke about the challenge to find and retain talented programmers, highlighting a few characteristics that professionals seek in companies.

palestra-joel-1

One of the high points, in my opinion, was the idea that companies that try to save with tools and hardware pay a far higher price, since each delay in development process waste their more expensive resource: the developer time.

palestra-joel-3

If you participate of the hiring process in your company, I suggest strongly for you to watch another version of this talk available on YouTube.

Networking

The most funny part of this event was meeting Stack Overflow team in company’s booth, as some SO users I knew only through the site, and many other people passing by.

Have a look how busy they were in these photos!

stand-so-2

Actually, I believe this was one of the busiest booths. Those who arrived there early got t-shirts and other freebies, which unfortunately vanished very soon.

stand-so-3

I talked fairly with Gabe, the Community Manager from Stack Overflow in Portuguese, who is the (guilty) responsible for the smooth progress of the Portuguese-speaking community. I’ve also talked a bit with one developer of the SO team, which was experimenting React.js and go language in a new project.

I couldn’t help myself from taking a striking reminder of the event. In the photo below, from left to right, you can see me, Joel and Bruno (the other moderator from Stack Overflow in Portuguese).

luiz-joel-bruno

Official release of Stack Overflow in Portuguese

Stack Overflow in Portuguese went through private and public beta. Participating of TDC, as one of the major IT events in Brazil, was a way to officially release the site, after its initial success.

After TDC, SO held a fellowship for major users, including some new friends that were passing by. It was to be a closed event, but Joel just twitted to everyone. 😀

In this photo, some of the main site users (including me at left):

galera-confra-2

Stack Overflow no TDC 2014

No dia 6 de agosto (2014) participei do TDC (The Developer’s Conference) em São Paulo. Como um dos moderadores do Stack Overflow em Português, fui convidado diretamente pela equipe do SO, um dos principais patrocinadores do evento.

Para quem ainda não sabe, o site mais essencial a todo desenvolvedor, Stack Overflow, estreou este ano sua edição em Português. O intuito é disponibilizar muita informação útil e de qualidade, no consagrado formato de perguntas e respostas, para milhões de usuários que falam Português e não se sentem tão à vontade conversando em Inglês.

Palestras

Assisti algumas palestras muito interessantes, das quais destaco What Do Programmers Care About por Joel Spolsky, um dos fundadores do Stack Overflow e do reconhecido site joelonsoftware.com, na trilha Management 3.0.

Ele falou sobre o desafio de encontrar e reter programadores talentosos, destacando características que esses profissionais buscam nas empresas.

palestra-joel-1

Um dos pontos altos da palestra, em minha opinião, foi a ideia de que as empresas que tentam economizar com ferramentas e hardware acabam pagando um preço muito alto, já que os atrasos causados no desenvolvimento são desperdício do seu recurso mais caro: o tempo do desenvolvedor.

palestra-joel-3

Se você está envolvido com o processo seletivo da sua empresa, sugiro fortemente que veja uma versão resumida da palestra disponível no YouTube.

Networking

A parte mais divertida do evento foi conhecer pessoalmente a equipe do Stack Overflow no estande da empresa, além de alguns usuários com os quais apenas tive contato online e diversas outras figuras que passaram por lá.

Veja nas fotos a seguir como o estande estava movimentado!

stand-so-2

Na verdade, creio que foi um dos mais estandes mais visitados. Quem chegou cedo conseguiu pegar camisetas e alguns outros brindes, que infelizmente acabaram rapidamente.

stand-so-3

Pude conversar bastante com o Gabe, o Community Manager (Gerente de Comunidade) do Stack Overflow em Português, que é o (culpado) responsável pelo bom andamento da comunidade de fala portuguesa. Conversei um pouco também com um desenvolvedor da equipe do SO, que estava experimentando as bibliotecas React.js e a linguagem Go em um novo projeto.

Não pude resistir em ter uma recordação marcante do evento. Na foto abaixo, da esquerda para a direita, estamos eu, o Joel e o Bruno (o outro moderador do Stack Overflow em Português).

luiz-joel-bruno

Comemoração do Lançamento do Stack Overflow em Português

O Stack Overflow em Português passou pelas fases de beta privado e beta público, período embrionário do site. A participação no TDC foi uma forma de lançamento oficial, devido ao sucesso que o site vem obtendo.

Após o encerramento do TDC, o SO ainda promoveu uma confraternização para os principais usuários, incluindo alguns amigos que estavam por lá. Era para ser um evento fechado, mas o Joel foi meio fanfarrão e publicou no twitter. 😀

Na foto abaixo, alguns dos principais usuários do site, uma turminha da pesada que passa por altas aventuras respondendo perguntas práticas e teóricas de programação.

galera-confra-2

Transações distribuídas e processamento paralelo com Atomikos

distributed Atomikos é um software Java que, entre outras coisas, implementa os padrões JTA (Java Transaction API) e XA (eXtended Architecture, que suporta processamento de transações distribuídas).

Em geral, cada transação é associada à thread atual, de modo que os diversos métodos que atendem uma solicitação num servidor JEE podem compartilhá-la.

Entretanto, uma questão interessante do StackOverflow levantou a possibilidade de uma aplicação dividir uma operação atômica em tarefas delegadas a várias threads, porém compartilhando uma única transação global.

Bem, para fazer esse “desvio” da arquitetura original, a solução foi usar diretamente a API XA do Atomikos para incluir os DataSources das diferentes threads na transação principal.

Fiz um exemplo simples que implementa isso. O projeto está disponível no meu GitHub.

Implementação

Antes de mais nada, temos a inicialização do DataSource e do TransactionManager usando a API do Atomikos realizado na class AtomikosDataSource. Eis o trecho relevante:

// Atomikos implementations
private static UserTransactionManager utm;
private static AtomikosDataSourceBean adsb;

// initialize resources
public static void init() {
    utm = new UserTransactionManager();
    try {
        utm.init();
        adsb = new AtomikosDataSourceBean();
        adsb.setMaxPoolSize(20);
        adsb.setUniqueResourceName("postgres");
        adsb.setXaDataSourceClassName("org.postgresql.xa.PGXADataSource");
        Properties p = new Properties();
        p.setProperty("user", "postgres");
        p.setProperty("password", "0");
        p.setProperty("serverName", "localhost");
        p.setProperty("portNumber", "5432");
        p.setProperty("databaseName", "postgres");
        adsb.setXaProperties(p);
    } catch (SystemException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

Depois, implementei uma thread chamada Processamento que recebe a instância da transação (Transaction) principal. A interface Callable define que a thread é um tipo de tarefa que retorna um valor Integer. Eis o código:

private static class Processamento implements Callable<Integer> {

    private int id;
    private boolean falhar;
    private Transaction transaction;

    public Processamento(int id, boolean falhar, Transaction transaction) {
        this.falhar = falhar;
        this.transaction = transaction;
        this.id = id;
    }

    public Integer call() throws Exception {
        if (falhar) {
            throw new RuntimeException("Falhou inesperadamente!");
        }

        //enlist xa connection
        XAConnection xac = AtomikosDataSource.getDS().getXaDataSource().getXAConnection();
        synchronized (transaction) {
            transaction.enlistResource(xac.getXAResource());
        }

        //normal execution, update row with OK
        Connection c = xac.getConnection();
        Statement s = c.createStatement();
        s.executeUpdate("update teste set processado = 'ok' where id = " + id);
        s.close();
        c.close();

        //delist xa connection
        synchronized (transaction) {
            transaction.delistResource(xac.getXAResource(), XAResource.TMSUCCESS);
        }
        return id;
    }

}

Note que, ao invés de usar o JTA, estou usando diretamente a API do XA implementada pelo Atomikos.

A chamada AtomikosDataSource.getDS().getXaDataSource().getXAConnection() recupera uma conexão do XA, a qual é adicionada à transação principal com o comando transaction.enlistResource(xac.getXAResource()). Esta operação é chamada de alistamento (enlistment). Ao final do processamento da thread, o alistamento é desfeito.

Sincronizei alguns trechos pois obtive aleatoriamente alguns NullPointerException nos testes. Não cheguei a averiguar se é um bug do Atomikos ou se é by design, isto é, o objeto Transaction não é thread-safe.

Finalmente, implementei um método que inicia cinco instâncias da thread de processamento listada acima e posteriormente colhe os resultados. Se uma delas falhar, a transação global é desfeita (rollback). Veja o código abaixo:

public static int processar(boolean falhar) {
    int ok = 0;
    Transaction transaction = null;
    try {

        //start transaction
        AtomikosDataSource.getTM().begin();
        transaction = AtomikosDataSource.getTM().getTransaction();

        //create thread pool
        ExecutorService executor = Executors.newFixedThreadPool(5);
        List<Callable<Integer>> processos = new ArrayList<Callable<Integer>>();

        //create 5 threads, passing the main transaction as argument
        for (int i = 0; i < 5; i++) {
            processos.add(new Processamento(i + 1, i == 4 && falhar, transaction));
        }

        //execute threads and wait
        List<Future<Integer>> futures = executor.invokeAll(processos);

        //count the result; get() will fail if thread threw an exception
        Throwable ex = null;
        for (Future<Integer> future : futures) {
            try {
                int threadId = future.get();
                System.out.println("Thread " + threadId + " sucesso!");
                ok++; 
            } catch (Throwable e) {
                ex = e;
            }
        }

        if (ex != null) {
            throw ex;
        }

        //finish transaction normally
        transaction.commit();

    } catch (Throwable e) {

        e.printStackTrace();
        try {
            //try to rollback
            if (transaction != null) {
                AtomikosDataSource.getTM().rollback();
            }
        } catch (IllegalStateException e1) {
            e1.printStackTrace();
        } catch (SecurityException e1) {
            e1.printStackTrace();
        } catch (SystemException e1) {
            e1.printStackTrace();
        }

    }
    return ok;
}

Note que vários métodos possuem um parâmetro chamado falha. Ele será usado para gerar um cenário onde uma das threads irá gerar um erro e forçar o rollback das alterações das demais threads.

O método processar() retorna a quantidade de “sucessos”, isto é, threads que executaram sem erro, independentemente se a transação foi efetivada ou desfeita. Isso também será usado nos testes.

Testes

Fiz testes tanto de um cenário de sucesso quanto de falha para validar a solução.

No cenário de sucesso, cada uma das cinco threads atualiza uma linha da tabela TESTE com o valor ok e no final o método principal faz o commit da transação.

No cenário de falha, a última thread sempre lança uma exceção, forçando o rollback das demais. Note que a última thread criada não é necessariamente a última a ser executada.

O código de teste ficou muito simples. Veja:

public class AtomikosTest {

    @BeforeClass
    public static void init() {
        //create atomikos transaction manager and data source
        AtomikosDataSource.init();

    }
    @Before
    public void reset() {
        //recreate data of TEST table
        AtomikosDAO.resetTable();
    }

    @AfterClass
    public static void shutdown() {
        //close atomikos resources
        AtomikosDataSource.shutdown();
    }

    @Test
    public void sucesso() {
        //process 5 rows in 5 threads
        int okParcial = AtomikosDAO.processar(false);
        //should return 5 successes
        Assert.assertEquals(5, okParcial);
        //confirms in table, count 5 ok's
        Assert.assertEquals(5, AtomikosDAO.countOk());
    }

    @Test
    public void fail() {
        //process 5 rows in 5 threads, one should fail
        int okParcial = AtomikosDAO.processar(true);
        //should return 4 successes
        Assert.assertEquals(4, okParcial);
        //confirms in table, count zero ok's due to rollback
        Assert.assertEquals(0, AtomikosDAO.countOk());
    }

}

Notas sobre a configuração

Neste projeto, usei o servidor de banco de dados PostgreSQL como o recurso a participar da transação distribuída.

Foi necessário habilitar a configuração max_prepared_transactions no arquivo de configuração postgresql.conf com um valor maior que o número de participantes na transação distribuída. Sem isso, o PostgreSQL não será capaz de participar de transações desta natureza.

Considerações finais

Embora haja um crescente interesse sobre NoSQL e até NewSQL, transações ACID, como disponíveis nos SGBDRs tradicionais, são importantes em muitos cenários. Até por isso existem tutoriais sobre como simular uma transação com o conceito de two-phase commit em bancos de dados não transacionais como MongoDB.

Além disso, é importante ressaltar que cada participante de uma transação distribuída deve ser compatível com o protocolo XA. Infelizmente, alguns drivers de bancos de dados ou outras fontes de dados podem não ser compatíveis. Então, faça sua lição de casa e pesquise antes de sair implementando.


Este artigo foi baseado na minha resposta no StackOverflow em Português!

O que são e como funcionam transações em SQL?

Bancos de dados relacionais que operam no padrão SQL em geral são transacionais, isto é, eles permitem a execução de uma sequência de operações como um bloco indivisível de forma a garantir a integridade dos dados em um ambiente com acesso concorrente.

O Problema

Imagine três operações sequenciais que afetam a base de dados e não usamos uma transação para controlá-las. Vamos usar como exemplo um caso de uso comum de um e-commerce:

  1. Verificar se possui o produto em estoque
  2. Inserir um novo registro da compra
  3. Debitar o estoque

Agora vamos supor que dois clientes estão tentando finalizar suas compras neste e-commerce fictício. O servidor recebe duas requisições quase simultaneamente e começa a processar os pedidos na sequência apresentada acima. Os dois pedidos estão sendo processados paralelamente em threads diferentes. Tanto o cliente A quanto o cliente B selecionaram um produto com apenas uma unidade em estoque.

Podemos acabar com a seguinte linha de execução:

  1. Thread A verifica o estoque (passo #1) e insere o registro da compra (passo #2)
  2. thread A é bloqueada e B passa a ser executada
  3. Thread B verifica o estoque (passo #1), o qual ainda não foi debitado, e insere o registro da compra (passo #2)
  4. Thread B atualiza o estoque (passo #3), que agora fica zerado
  5. Thread B é bloqueada e A passa a ser executada
  6. Thread A atualiza o estoque (passo #3), que agora fica negativo!

Apesar de verificarmos o estoque a ordem de execução dos diferentes processos é imprevisível, então neste cenário concorrente ela não traz garantia do valor no passo seguinte.

A Solução

Bancos de dados transacionais usam o conceito ACID:

  • Atomicidade: uma transação é uma sequência de operações indivisível, ou é executado como um todo, ou tudo é desfeito.
  • Consistência: ao final da transação, o estado dos dados deve ser consistente.
  • Isolamento: embora alguns sistemas permitam quebrar o isolamento, em geral, uma transação em andamento não pode ser acessada por outras transações de modo a evitar leitura de um estado inconsistente, uma “sujeira”.
  • Durabilidade: em caso de sucesso (commit) a persistência dos dados deve ser garantida

Para garantir esses conceitos, em geral, os bancos de dados usam bloqueios quando ocorrem acessos simultâneos à mesma estrutura de dados. Ou seja, se alguém já está mexendo nos dados, os demais tem que aguardar sua vez numa fila até ele acabar.

Na prática

Ao usar bancos de dados transacionais, nós podemos usufruir deste controle de gerenciamento por parte dos SGBDRs (Sistemas Gerenciadores de Bancos de Dados Relacionais).

Incluindo o conceito de transação ACID no exemplo anterior, vamos ver como fica a execução:

  1. Thread A inicia uma transação, verifica o estoque (passo #1) e insere o registro da compra (passo #2)
  2. Thread A é bloqueada e B passa a ser executada
  3. Thread B inicia uma transação, mas ao tentar verificar o estoque ela é bloqueada porque a transação de A ainda não acabou
  4. Thread A atualiza o estoque, que agora fica zerado, e faz commit na transação.
  5. Thread B é desbloqueada e passa a ser executada
  6. Thread B conclui a verificação do estoque (passo #1) e retorna um erro pois não encontra o produto disponível no estoque
  7. Thread B executa um rollback para desfazer outras alterações que tenha efetuado, por exemplo, se houve outro produto debitado na mesma transação.

O resultado final é como se somente a thread A tivesse executado e B nunca existisse.

Nem tudo é um mar de rosas

Existem alguns problemas inerentes a transações ACID, sendo o desempenho o maior deles.

Embora seja importante garantir a integridade dos dados, para muitos sistemas onde a disponibilidade é o fator mais crítico um modelo que bloqueia acessos simultâneos torna-se inviável. Este é um dos principais fatores para o surgimento e a adoção de diversos sistemas de bancos de dados não transacionais e NoSQL.

O importante é entender que o uso de transações tem um custo e em algumas ocasiões este pode ser alto demais. Uma das representações mais comuns do trade-off de persistência de dados é a seguinte (retirada deste artigo):

tradeoff-database

O gráfico demonstra que consistência, disponibilidade e particionamento (escalar o banco de dados em diversos nós) são recursos que afetam uns aos outros. Você simplesmente não pode ter o melhor dos três e isso foi provado no teorema de CAP.

Bancos de dados relacionais geralmente sacrificam o particionamento em prol da consistência e da disponibilidade, enquanto alguns sistemas NoSQL sacrificam a consistência dos dados.

Note que o termo “geralmente” ou “em geral” foi cuidadosamente utilizado no artigo porque os diferentes sistemas de bancos de dados permitem vários níveis de configuração de isolamento de transações e acesso simultâneo, possibilitando um ajuste fino do desempenho de cada funcionalidade

Este artigo foi baseado na minha resposta no StackOverflow em Português!

Como desabilitar campos em sistemas web

Quando lidamos com sistemas web, é frequente a necessidade de desabilitarmos algum campo na página, com a finalidade de impedir a alteração por parte do usuário.

O problema é que o navegador não envia campos desabilitados com disabled na submissão do formulário. Esta característica comumente deixa os novatos atordoados.

Solução: readonly

Uma solução é usarmos o atributo readonly, porém existem efeitos colaterais:

  • Se for um campo de texto, o usuário poderá selecionar e copiar o conteúdo, o que pode ser uma vantagem em alguns casos.
  • Além disso, os estilos do campo permanecerão os originais, isto é, o usuário poderá não perceber que se trata de um campo somente-leitura.

Para contornar o segundo ponto e dar feedback visual para o usuário, adicione um estilo para manter a usabilidade, por exemplo, deixando o fundo da caixa um pouco acinzentado.

Exemplo com CSS inline:

<input type="text" value="Valor fixo" 
    style="background: #EEE; cursor: not-allowed; color: #777"
    readonly />

Exemplo com uma classe CSS:

.desabilitado {
    background: #EEE; 
    cursor: not-allowed; 
    color: #777;
}

<input type="text" value="Valor fixo 2" class="desabilitado" readonly />

Veja o exemplo funcional do código acima no jsfiddle.

Considerações de segurança

Alguém pode argumentar que desabilitar campos na tela não é uma boa prática do ponto de vista de Segurança da Informação, pois hoje um usuário pode facilmente burlar essa limitação através da Ferramenta do Desenvolvedor (F12), alterando as propriedades do campo. Isso está correto, em parte.

O importante é entender que a necessidade de desabilitar campos é parte essencial do funcionamento dos sistemas. Não é seu objetivo garantir a segurança da informação daquele campo.

Este artigo tem por objetivo auxiliar o desenvolvedor front-end com um elemento puramente visual e não isenta as validações necessárias no back-end.

Entendido!?

Este artigo foi baseado na minha resposta no StackOverflow em Português!

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.