Estou com uma dúvida sobre qual a melhor maneira para escrever um comando na seguinte situação.
Tenho uma estrutura de dados complexa no meu sistema prevalente. Faço uma busca nela atrás do Objecto X. Acho o objeto X, legal.
Aí eu quero fazer uma alteração nesse objeto X.
X.setAge(26);
Como eu escrevo um comando pra isso ??? Minha dúvida é: Vou ter que fazer a busca novamente dentro do comando antes de alterar o objeto ???
A solução direta que veio na minha cabeça foi passar o Objeto para dentro do comando. Mas aí problemas podem ocorrer !!!
Num eventual restart, vc pode acabar com uma instância isolada dentro do seu comando, fazendo com que a sua alteração não tenha efeito sobre o objeto que está efetivamente no sistema. Os comandos precisam ser aplicados no sistema prevalente (no caso do Space4J no Space) e não num objeto qualquer que é passado para dentro do comando.
Então fica a pergunta: Depois de fazer uma mega query, eu fico com um objeto X em mãos. Como alterá-lo através de um comando sem incorrer no problema acima ?
Crie um objeto que represente sua query, seja serializavel e contenha apenas valores. Desta forma voce pode executar sua query de dentro do teu comando.
Junte a isso um campo transiente com o objeto encontrado e a query vai precisar ser executada novamente apenas durante replay.
Note que sua query pode ser a mesma executada por algum business method ou uma ‘fast-lookup’.
Ou seja, voce pode tanto usar a query que aconteceu no teu business model: “quero uma kombi preta, ano 81 e placa da regiao norte” ou uma fast-lookup “quero o carro da placa foo 2233”; a segunda query é mais rápida pois vai diretamente contra um índice.
Exemplo:
interface Query implements Serializable {
Object execute();
}
class BuyCarCommand implements Command {
Query query;
transient Car car;
public BuyCarCommand(Query query, Car car) {
this.query = query;
this.car = car;
}
public void execute() {
if(car == null)
car = (Car)query.execute();
car.buy();
}
}
Jeito difícil: sim, vc vai ter que buscar o objeto de novo, ou vc cai no “problema do batismo”.
Jeito viagem-mas-que-da-certo: passe pro comando uma String com uma expressao OGNL que “ensina” ao comando como buscar o objeto, e novo valor da propriedade “age”
Falando em usar objetos imutaveis, me dei conta que cada vez mais eu estou programando de forma funcional em java.
Tou desenvolvendo para eclipse e quase todo meu domain model é composto por objetos imutaveis com métodos que tem quase todas variaveis e argumentos imutaveis.
A solução do louds é muito legal, principalmente a sacada do transient !!! A desvantagem é eu ter que transformar minha procura num objeto pra poder passá-la pra dentro do comando.
Se o objeto fosse imutável como o cv sugeriu, de qualquer maneira eu teria que fazer a procura de novo, pois teria que substituir o objeto velho pelo objeto novo. No caso aí não seria uma procura, mas sim um update !!!
Se tiver um jeito de acessar esse objeto por uma OID, seria bem mas trivial, o problema que isso nem sempre será possível.
Tive uma sacada muito louca, não pensei muito mas aí vai. :mrgreen:
CRIAR UMA MEMÓRIA TEMPORÁRIA NO SISTEMA PREVALENTE, QUE PODE SER APAGADA DEPOIS DE CADA SNAPSHOT !!!
Então estou com o meu objeto X em mãos. Registro ele nessa memória temporária e recebo um ID único para acessá-lo. Passo esse ID único pra dentro do comando de forma que eu posso recuperar o objeto lá dentro, direto do sistema prevalente.
Num eventual replay, essa memória temporária ainda estará lá.
Depois de um snapshot, esses comandos não serão mais executados, então podemos nos livrar dessa memória temporária.
E o melhor, essa memória temporária não ocuparia muito espaço, pois conteria apenas referencias para objetos já existentes.
Se voce quiser brincar com serialização, pode fazer o seguinte:
interface Identifiable {
//tem de ser possivel localizar este objeto somente pelo valor do Id
Serializable getId();
}
class IdentifiableProxy implements Serializable {
public final Serializable id;
public IdentifiableProxy(Serializable id) { this.id = id; }
}
class SmartObjectOutputStream extends ObjectOutputStream {
public SmartObjectOutputStream(OutputStream output) throws IOException {
super(output);
enableReplaceObject(true);
}
protected Object replaceObject(Object obj) throws IOException {
if(obj instanceof Identifiable)
return new IdentifiableProxy( ((Identifiable).obj).getIdentification() );
else
return obj;
}
}
//O ObjectInputStream funciona de forma analoga, substituindo os IdentifiableProxy pelos objetos reais.
A sacada é bem simples, ela permite que o teu comando guarde diretamente referencias para objetos do teu sistema prevalente e o mecanismo de replaceObject cuida apenas serializar um ‘cookie’ no lugar de cada um. Desta forma o atributo não precisa mais ser transiente.
Esse mecanismo tem apenas um porém, o código precisa da permissão de enableSubstitution, isso pode criar problemas em alguns ambientes, como JWS por exemplo.
Outra coisa interessante que não cheguei a investigar é usá-lo para criar alguma forma de schema migration declarativa usando uma mistura de classloaders com replaceObject.
Não acho pq não vejo essa estrategia funcionando. Alem do fato que ter meu model model cheio de código com id’s me faz querer arremessar o computador contra a parede.
Argumentos? Fatos? Por que vc acha que não vai funcionar? Não vai funcionar porque não vai funcionar? Pra mim já está funcionando. 8)
Arremessar o computador na parede por causa de 3 linhas de código, me parece um exagero. Até porque vc não vai precisar se preocupar com os OIDs, tudo que vc vai fazer é repassá-los para os seus comandos.
O que é melhor para um comando? Receber um objeto query e ter que re-executar uma query desnecessária pra achar o objeto, ou receber um int e pegar esse objeto de uma HashMap?
Uma memória temporária me parece uma solução bastante interessante. A sua solução, além de bem mais complexa, tem aquele problema de segurança que vc mesmo falou.
Se dá pra resolver o problema de uma maneira simples e eficiente, melhor não complicar, a não ser que tenhamos um argumento forte para tal.
-adicionar pera a memoria temporaria
-executar FooTransaction(pera, peraId)
Esquecendo um pouco que podemos fazer alguns truques para termos transaction merging. O problema é que ainda vamos ter que pensar em alguma forma de salvar um ‘cookie’ para pera, e com isso voltamos ao problema original.
Ou seja, essa solução, ao meu ver, é uma não-solução, pois em nada colabora em resolver o problema. Ou será que eu não percebi alguma coisa?
Ele é parte integrante do sistema prevalente, ou seja, por trás dos panos vc está usando comandos para colocar seus objetos nele.
space4j.addToTempMem(X), na verdade executa um comando para adicionar o objeto nessa map temporária e retorna um ID único (increment) que será a key desse objeto no map.
Então ele fica persistido como qualquer outro objeto. A vantagem é que após um snapshot vc pode limpar ele, já que vc não vai mais dar um replay nos comandos.
Esse Map temporário nada mais é do que uma facilidade para o sistema, de forma que vc tem uma maneira fácil e rápida de encontrar o seu objeto dentro do sistema prevalente. Haverá casos em que isso não será possível facilmente, e teremos que reexecutar a nossa busca, ou partir para o esquema de serialização proposto pelo louds.
Mais uma coisa legal: Esse map fica numa área protegida do Space, ou seja, não tem como um usuário engraçadinho dar um get nele e sair zoando. 8)
Mas eu ainda não entendi qual o problema desse processo não ser transacional, isto é, de termos duas transações em sequência ???
Quando a primeira transação retornar, vc poderá ter certeza que o seu objeto está e estará lá para sempre, e que a partir de agora vc pode acessá-lo no Space através do ID retornado.
Vc não vai precisar passar o seu objeto para dentro do comando como vc ilustrou no seu exemplo, apenas o ID. O ID é o cookie !!!
Então qual é o problema ??? Vc poderia me explicar isso melhor ??? Pode ser que eu não esteja vendo alguma coisa, ou que vc está com um resistência a uma idéia simples que pode ser uma não-não-solução.
[quote=“saoj”]
Então qual é o problema ??? Vc poderia me explicar isso melhor ??? Pode ser que eu não esteja vendo alguma coisa, ou que vc está com um resistência a uma idéia simples que pode ser uma não-não-solução.[/quote]
Voce disse que space4j.addToTempMem(X) executa um comando por baixo dos panos. Me mostra como esse comando é implementado, já que essa é a parte que eu não entendi.
Acho que agora eu comecei a entender aonde vc quer chegar. O problema do replay pode se repetir com o comando abaixo, ou seja, minha memória temporária pode acabar apontando para uma cópia do objeto.
Então vc tinha razão !!! Desculpe insistir no ponto, mas eu realmente não estava vendo que o problema se repetiria no comando da memória temporária. Estar errado e assumir o erro faz parte do jogo !!!
Bastava vc dizer: “Vc vai ter o mesmo problema para criar a memória temporária, ou seja, no replay o seu comando pode criar uma memória temporária com o objeto isolado.”
Talvez isso fosse óbvio pra vc e vc achou que poderia ser pra mim tb. De qualquer forma valeu pelo papo !!!
package org.space4j.commands;
import org.space4j.*;
import java.util.*;
public class AddToTempMemCmd extends Command {
private Object obj;
public AddToTempMemCmd(Object obj) {
this.obj = obj;
}
public int execute(Space space) throws CommandException {
Integer id = (Integer) space.getObject("sys_tempMemId");
if (id == null) id = new Integer(0);
int newid = id.intValue() + 1;
HashMap map = (HashMap) space.getObject("sys_tempMemMap");
if (map == null) {
map = new HashMap();
space.putObject("sys_tempMemMap", map);
}
Integer oid = new Integer(newid);
map.put(oid, obj);
space.putObject("sys_tempMemId", oid);
return newid;
}
}
A idéia é a mesma por trás dos objetos imutáveis: seu comando não tem acesso direto ao objeto, ele precisa procurá-lo. Mas uma instância que vc mantenha ali pode servir como argumentos pro seu “query by example”.
Foca no business, cara… pq c tá tentando eliminar ou dar a volta em um dos pontos que explica porque prevalência funciona…
A solução do Louds funciona perfeitamente enquanto o argumento que vc passa pro comando for exatamente o seu BO. Mas pode acontecer dos seus comandos nunca terem acesso a eles, daí vc tem que ficar com a query mesmo.
Se vc quer um acesso O(1) a um objeto em uma estrutura de objetos qualquer, o que vc faz?? Indexa! Não acho que todos os objetos precisem de OID, mas se esse for um gargalo no seu sistema, é uma saída honrosa.
Pra mim, é difícil acreditar que ter sempre um índice e um OID seja vantajoso sobre uma query baba que normalmente a gente sabe fazer.
A solução do louds é realmente a melhor. De posse do objeto, haverá N maneiras de eu conseguir chegar a ele no Space. Ou através da mesma query, ou melhor ainda, através da OID. A sacada do transiente tb faz todo o sentido.
Já criei para o Space4J um framework de indexação, pois acho que os bancos de dados em matéria de indexação fazem um excelente trabalho de abstração de complexidade. Meu objetivo, um pouco ambicioso, é obter esse mesmo nível de transparência com o Space4J. Estou usando o pattern Observable + Reflection para notificar o sistema de que o objeto mudou e precisa ser re-indexado. Dessa maneira o programador pode esquecer a indexação enquanto trabalha com o seu objeto.
Uma coisa que o Space4J ainda não tem, e talvez tenha que ter é um framework para queries. Queries simples podem ser feitas na mão mesmo, sem problemas, tanto pelo programador quanto pelo Comando. Mas quando tivermos queries mais complexas, um objeto Query como o louds falou cairá muito bem.