O Cassandra, o banco de dados não relacional orientados à família de colunas, é um banco feito para trabalhos de alta performance, alta escalabilidade horizontal além de ser tolerante a falhas. Uma das grandes vantagens de sua arquitetura de escalabilidade horizontal é que se pode fazer com que cresça o número de nós a partir de uma alta demanda e diminuir em períodos de pouco processamento, ideal para a plataforma nas nuvens tão comumente discutido. Em sua nova versão trouxe o novo Cassandra Query Language, CQL, a versão 3.0, que dentre suas melhorias está no trabalho de coleções, chaves compostas.

Com tantas melhorias o único problema é a maneira de tirar proveito de maneira eficiente desses novos recursos, afinal o foco do desenvolvedor Java deve ser solução em OO e sobre tudo no objetivo da aplicação, regras de negócio, a ponto de que esse parser entre banco e negócio seja o menor possível. Com esse objetivo surgiu o Easy-Cassandra, seu foco é facilitar o desenvolver Java utilizar esses recurso sem em nenhum momento desfocar no objetivo e no negócio da aplicação. Nessa nova versão o framework ORM trouce as seguintes melhorias:

  • Chave Composta
  • Mapeamento de coleções( java.util.List, java.util.Set, java.util.Map)
  • Reconhecimento automático dos demais cluters na conexão e trabalho de autobalanceamento.
  • Melhorias no levantamento da aplicação.
  • Criação do Schema, para o uso de keyspace diferente do passado.

Nessa nova versão houve algumas quebras de retrocompatibilidades, isso deve ao uso do java Driver e de suas consultas totalmente focadas no CQL 3. Essa pequena quebra está apenas no modo em que é criado a classe ponte entre o cassandra e o seu objeto java já que as anotações de mapeamento continuam tendo como base as anotações do JPA 2.0.

Como a ideia no futuro é que exista o banco MongoDB, foi criada a interface org.easycassandra.persistence.Persistence, assim os novos bancos de dados e o Cassandra terão esse contrato, assim será possível, trocando apenas a implementação, mudando o banco de dados não relacional.

Listagem 1: Interface org.easycassandra.persistence.Persistence


public interface Persistence {

    boolean insert(Object bean);

    boolean delete(Object bean);

    boolean update(Object bean);

     List findAll(Class bean);

     T findByKey(Object key, Class bean);

    boolean deleteByKey(Object key, Class bean);

    boolean executeUpdate(String query);

}
A interface do cassandra org.easycassandra.persistence.cassandra.PersistenceCassandra, como todos os novos bancos compatíveis herda do Persistence e adiciona alguns pequenos recursos.

Listagem 2: Interface do Cassandra herdando de Persistence


public interface PersistenceCassandra extends Persistence {

     List findByIndex(Object index, Class bean);

    Long count(Class bean);
}

A classe responsável por gerar a implementação continua sendo a org.easycassandra.persistence.cassandra.EasyCassandraManager, mas ela se tornou um enum, sim isso mesmo um enum!, já que o objetivo é que ele seja único nada mais natural que o singleton natural cedido pela própria JVM. Só é necessário passar o host e o keyspace, caso não existe ele se responsabilizará por você. Para adicionar objetos mapeados no Cassandra basta usar o método addFamilyObject passando o .class, assim como no caso do keyspace, caso ele não existe ele se responsabilizará em criar ou alterar a column family, para o seu objeto.

Como base nisso o DAO base poderia ficar da seguinte maneira:

Listagem 3: Dao base




public class PersistenceDao {

    private static final String KEY_SPACE = "javabahia";
    private static final String HOST = "localhost";
    private PersistenceCassandra persistence;
    private Class baseClass;
    

    public PersistenceDao(Class baseClass) {
        this.baseClass = baseClass;
        persistence = EasyCassandraManager.INSTANCE.getPersistence(HOST, KEY_SPACE);
        EasyCassandraManager.INSTANCE.addFamilyObject(baseClass, KEY_SPACE);
        
    }

    public boolean insert(T bean) {
        return persistence.insert(bean);
    }

    public boolean remove(T bean) {
        return persistence.delete(bean);
    }

    public boolean removeFromRowKey(K rowKey) {
        return persistence.deleteByKey(rowKey, baseClass);
    }

    public boolean update(T bean) {
        return persistence.update(bean);
    }

    public T retrieve(Object id) {
    	
        return   persistence.findByKey(id, baseClass);
    }

    public List listAll() {
        return persistence.findAll(baseClass);
    }

    public List listByIndex(Object index) {
        return persistence.findByIndex(index, baseClass);
    }

    public Long count() {
        return persistence.count(baseClass);
    }

    public boolean executeUpdateCql(String string) {
        return persistence.executeUpdate(string);
    }

}

Uma outra estratégia seria a mudança de interface para a persistence, assim esse DAO seria para todos os bancos de dados nosql que o framework for sendo compatível. Uma vez pronto o nosso projeto base, vamos falar um pouco sobre o mapeamento dos objetos. Os desenvolvedores java que conhecem o JPA já estarão familiarizado como o modelo de mapeamento.

Listagem 4: Mapeamento


@Entity(name = "person")
public class Person implements Serializable {

    private static final long serialVersionUID = 3L;
    
    @Id
    private Long id;
    
    @Index
    @Column(name = "name")
    private String name;
    
    @Column(name = "born")
    private Integer year;
    
    
    @Enumerated
    private Sex sex;
    
    
    @Embedded
    private Address address;

//getter and setter
}

Assim os campos são:

  • @Id serve para indicar que esse campo será a chave da linha.
  • @Enumerated indica que o campo é um enum.
  • @Embedded indica que existe um objeto complexo e este será embitudio na mesma linha.
  • @Index indica o índice, funciona como um segundo ID, assim será possível realizar busca a partir desse campo.
  • @Table ou @Entity indica que o objeto é mapeado e pega o nome da column family
  • @EmbeddedId indica que esse campo é um objeto complexo e nele está embutido as chaves da column family

Listagem 5: Classe Linux Distribution



@Entity(name="linux")
public class LinuxDistribuition {

    @EmbeddedId
    private IdLinux id;
    
    @Column
    private String guy;
    
    @Column
    private String version;
    
    @Column(name="descriptions")
    private String descriptions;
//getter and setter
}

public class IdLinux {
    
    
    @Column
    private String name;
    
    @Column
    private String kernelVersion;
//getter and setter
}

Além desses campos essa nova versão trouxe novas anotações para se poder trabalhar com listas dentro do cassandra que são:

  • SetData indica que o campo é uma interface java.util.Set, é necessário informar o classeData na qual indica o tipo do objeto que terá no set.
  • ListData indica que o campo é uma interface java.util.List, é necessário informar o classeData na qual indica o tipo do objeto que terá na lista.
  • MapData indica que o campo é uma interface java.util.Map, é necessário informar a chave e o valor do map informando o classkey e classValue respectivamente.

Exemplos do uso dessa interação abaixo:

Listagem 6: Exemplo de interações


@Entity(name="resumebook")
public class Book {

    @Id
    @Column(name="booksname")
    private String name;
    
    @MapData(classKey=Long.class,classValue=String.class)
    private Map chapterResume;
//getter and setter
}


@Entity(name="shopping")
public class ShoppingList {

    @Id
    private String name;
    
    @Column(name="day")
    private Date day;
    
    @ListData(classData=String.class)
    @Column(name="frutis")
    private List fruits;
    
    @Column(name="storeName")
    private String storeName;
//getter and setter
}

@Entity(name="contact")
public class Contact implements Serializable {

    @Id
    @Column(name="id")
    private String name;
    
    
    @Column(name="emails")
    @SetData(classData=String.class)
    private Set emails;
//getter and setter
}

Uma vez mapeado e com o DAO pronto o seu uso se torna muito fácil, já que as buscas são semelhantes a única diferença está no objeto mapeado e o retorno.

Listagem 7: DAO Mapeado



public class BookDAOTest {

 private PersistenceDao persistence=new PersistenceDao(Book.class);
    
    
    @Test
        public void insertTest() {
        Book book = getBook();
        Assert.assertTrue(persistence.insert(book));
    }


    private Book getBook() {
        Book book = new Book();
        book.setName("Cassandra Guide ");
        Map resumeChapter=new HashMap();
        resumeChapter.put(1l, "this chapter describes new resources in cassandra and improvements with CQL");
        resumeChapter.put(2l, "Understanding the architecture");
        resumeChapter.put(3l, "Installing DataStax Community");
        resumeChapter.put(4l, "Upgrading Cassandra");
        resumeChapter.put(5l, "Initializing a cluster");
        resumeChapter.put(6l, "Security");
        resumeChapter.put(7l, "Database design");
        resumeChapter.put(8l, "Using the database");
        resumeChapter.put(9l, "Database internals");
        resumeChapter.put(10l, "Configuration");
        resumeChapter.put(11l, "Operations");
        resumeChapter.put(12l, "Backing up and restoring data");
        resumeChapter.put(13l, "Cassandra tools");
        resumeChapter.put(14l, "Troubleshooting");
        resumeChapter.put(14l, "Troubleshooting");
        resumeChapter.put(15l, "References");
        book.setChapterResume(resumeChapter);
        return book;
    }
    
    
    @Test
    public void retrieveTest() {
        Book book=persistence.retrieve(getBook().getName());
        Assert.assertNotNull(book);
     
    }
    
    @Test
    public void removeTest() {
        persistence.remove(getBook());
        Assert.assertNull(persistence.retrieve(getBook().getName()));
        persistence.insert(getBook());
    }
    
}


public class ContactsDAOTest {
 private PersistenceDao persistence=new PersistenceDao(Contact.class);
    
    
    @Test
        public void insertTest() {
        Contact contacts = getContact();
        Assert.assertTrue(persistence.insert(contacts));
    }


    private Contact getContact() {
        Contact contacts = new Contact();
        contacts.setName("Shrek ");
        contacts.setCyte("far far away");
        contacts.setEmails(new HashSet());
        contacts.getEmails().add("shreck@shrek.org");
        contacts.getEmails().add("shreck@farfaraway.far");
        return contacts;
    }
    
    
    @Test
    public void retrieveTest() {
     Contact contact=persistence.retrieve(getContact().getName());
     Assert.assertNotNull(contact);
     
    }
    @Test
    public void removeTest() {
        persistence.remove(getContact());
        Assert.assertNull(persistence.retrieve(getContact().getName()));   
    }
    
}



public class ShoppingListDAOTest {

    private PersistenceDao persistence=new PersistenceDao(ShoppingList.class);
    
    
    @Test
    public void insertTest() {
        
        Assert.assertTrue(persistence.insert(getPolianaShoppingList()));
    }
    private ShoppingList getPolianaShoppingList() {
        ShoppingList shopping=new ShoppingList();
        shopping.setDay(new Date());
        shopping.setName("Poliana");
        shopping.setFruits(new LinkedList());
        shopping.getFruits().add("Orange");
        shopping.getFruits().add("beans");
        shopping.getFruits().add("rice");
        return shopping;
    }
    
    @Test
    public void retrieveTest() {
     ShoppingList list=persistence.retrieve(getPolianaShoppingList().getName());
     Assert.assertNotNull(list);
    }
    @Test
    public void removeTest() {
        ShoppingList list=persistence.retrieve(getPolianaShoppingList().getName());
        persistence.remove(list);
        list=persistence.retrieve(getPolianaShoppingList().getName());
        Assert.assertNull(list);
    }
    
}

Outro recuso importante é que foi solicitado pela comunidade, abraços ao pessoal da Bern na Suiça, é a possibilidade de usar outro keyspace diferente do definido no persistence. Para isso basta usar o schema na anotação @Table como mostra o exemplo abaixo:

Listagem 8: Usando o schema


@Table(name="drink",schema="schemaA")
public class Drink implements Serializable {

		@Id
	    private Long id;
	    
	    @Index
	    @Column(name = "name")
	    private String name;
	    
	    @Column(name = "flavor")
	    private String flavor;

A nova versão do Easy-Cassandra, 2.0.0, ainda está em fase beta, assim é necessário realizar mais testes, mas com isso tem noção do que vem está por vir. Uma das coisas legais do framework é pioneirismos do projeto, até então ele é o primeiro ORM do cassandra que utiliza os recursos do 100% CQL 3.0. Isso graças ao grande trabalho por parte da comunidade.

Nesse link você pode conferir o projeto no GitHub: https://github.com/otaviojava/Easy-Cassandra