Operações de lote assíncronas no Couchbase
1. Introdução
Neste acompanhamento de nosso tutorial emusing Couchbase in a Spring application, exploramos a natureza assíncrona do Couchbase SDK e como ele pode ser usado para executar operações de persistência em lotes, permitindo assim que nosso aplicativo alcance o uso ideal dos recursos do Couchbase.
1.1. InterfaceCrudService
Primeiro, aumentamos nossa interfaceCrudService genérica para incluir operações em lote:
public interface CrudService {
...
List readBulk(Iterable ids);
void createBulk(Iterable items);
void updateBulk(Iterable items);
void deleteBulk(Iterable ids);
boolean exists(String id);
}
1.2. InterfaceCouchbaseEntity
Definimos uma interface para as entidades que queremos persistir:
public interface CouchbaseEntity {
String getId();
void setId(String id);
}
1.3. ClasseAbstractCrudService
Em seguida, implementaremos cada um desses métodos em uma classe abstrata genérica. Esta classe é derivada da classePersonCrudService que usamos emthe previous tutorial e começa da seguinte maneira:
public abstract class AbstractCrudService implements CrudService {
private BucketService bucketService;
private Bucket bucket;
private JsonDocumentConverter converter;
public AbstractCrudService(BucketService bucketService, JsonDocumentConverter converter) {
this.bucketService = bucketService;
this.converter = converter;
}
protected void loadBucket() {
bucket = bucketService.getBucket();
}
...
}
2. A Interface de Bucket Assíncrona
O Couchbase SDK fornece a interfaceAsyncBucket para realizar operações assíncronas. Dada uma instânciaBucket, você pode obter sua versão assíncrona por meio do métodoasync():
AsyncBucket asyncBucket = bucket.async();
3. Operações em lote
Para realizar operações em lote usando a interfaceAsyncBucket, usamos a bibliotecaRxJava.
3.1. Leitura em lote
Aqui, implementamos o métodoreadBulk. Primeiro usamos o mecanismoAsyncBucketeflatMap em RxJava para recuperar os documentos de forma assíncrona emObservable<JsonDocument>, então usamos o mecanismotoBlocking emRxJava para converter estes para uma lista de entidades:
@Override
public List readBulk(Iterable ids) {
AsyncBucket asyncBucket = bucket.async();
Observable asyncOperation = Observable
.from(ids)
.flatMap(new Func1>() {
public Observable call(String key) {
return asyncBucket.get(key);
}
});
List items = new ArrayList();
try {
asyncOperation.toBlocking()
.forEach(new Action1() {
public void call(JsonDocument doc) {
T item = converter.fromDocument(doc);
items.add(item);
}
});
} catch (Exception e) {
logger.error("Error during bulk get", e);
}
return items;
}
3.2. Inserção em lote
Usamos novamente a construçãoRxJava’s flatMap para implementar o métodocreateBulk.
Como as solicitações de mutação em massa são produzidas mais rápido do que suas respostas podem ser geradas, às vezes resultando em uma condição de sobrecarga, instituímos uma nova tentativa com atraso exponencial sempre que umBackpressureException é encontrado:
@Override
public void createBulk(Iterable items) {
AsyncBucket asyncBucket = bucket.async();
Observable
.from(items)
.flatMap(new Func1>() {
@SuppressWarnings("unchecked")
@Override
public Observable call(final T t) {
if(t.getId() == null) {
t.setId(UUID.randomUUID().toString());
}
JsonDocument doc = converter.toDocument(t);
return asyncBucket.insert(doc)
.retryWhen(RetryBuilder
.anyOf(BackpressureException.class)
.delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
.max(10)
.build());
}
})
.last()
.toBlocking()
.single();
}
3.3. Atualização em lote
Usamos um mecanismo semelhante no métodoupdateBulk:
@Override
public void updateBulk(Iterable items) {
AsyncBucket asyncBucket = bucket.async();
Observable
.from(items)
.flatMap(new Func1>() {
@SuppressWarnings("unchecked")
@Override
public Observable call(final T t) {
JsonDocument doc = converter.toDocument(t);
return asyncBucket.upsert(doc)
.retryWhen(RetryBuilder
.anyOf(BackpressureException.class)
.delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
.max(10)
.build());
}
})
.last()
.toBlocking()
.single();
}
3.4. Eliminação em lote
E escrevemos o métododeleteBulk da seguinte maneira:
@Override
public void deleteBulk(Iterable ids) {
AsyncBucket asyncBucket = bucket.async();
Observable
.from(ids)
.flatMap(new Func1>() {
@SuppressWarnings("unchecked")
@Override
public Observable call(String key) {
return asyncBucket.remove(key)
.retryWhen(RetryBuilder
.anyOf(BackpressureException.class)
.delay(Delay.exponential(TimeUnit.MILLISECONDS, 100))
.max(10)
.build());
}
})
.last()
.toBlocking()
.single();
}
4. PersonCrudService
Finalmente, escrevemos um serviço Spring,PersonCrudService, que estende nossoAbstractCrudService para a entidadePerson.
Como toda a interação do Couchbase é implementada na classe abstrata, a implementação para uma classe de entidade é trivial, pois precisamos garantir apenas que todas as nossas dependências sejam injetadas e nosso bucket carregado:
@Service
public class PersonCrudService extends AbstractCrudService {
@Autowired
public PersonCrudService(
@Qualifier("TutorialBucketService") BucketService bucketService,
PersonDocumentConverter converter) {
super(bucketService, converter);
}
@PostConstruct
private void init() {
loadBucket();
}
}
5. Conclusão
O código-fonte mostrado neste tutorial está disponível emgithub project.
Você pode aprender mais sobre o Couchbase Java SDK noCouchbase developer documentation site oficial.