At line 8 changed 2 lines. |
In the context of [AppFuse], this is called a Manager class. It's main responsibility to act as a bridge between the persistence (DAO) layer and the |
web layer. The [Business Delegate|http://java.sun.com/blueprints/corej2eepatterns/Patterns/BusinessDelegate.html] pattern from Sun says that these objects are useful for de-coupling your presentation layer from your database layer (i.e. for Swing apps). Managers should also be where you put any business logic for your application. |
No contexto do [AppFuse], isto é chamado de classe Manager(Gerente). Sua responsabilidade principal é agir como uma ponte entre a camada de persistência(DAO) e a camada web. O padrão [Business Delegate|http://java.sun.com/blueprints/corej2eepatterns/Patterns/BusinessDelegate.html] da Sun dita que estes objetos são úteis para desacoplar a camada de apresentação da camada de dados (i.e. para aplicações Swing). Gerentes(Managers) devem ser colocados onde lógicas de negócio são necessárias. |
At line 11 changed 1 line. |
;:%%(color: blue)''I will tell you how I do stuff in the __Real World__ in text like this.''%% |
;:%%(color: blue)''Vou dizer a vocês como faço as coisas no __Mundo Real__ em textos como este.''%% |
At line 13 changed 1 line. |
Let's get started by creating a new ManagerTest and Manager in AppFuse's architecture. |
Vamos começar criando novas classes ManagerTest e Manager na arquitetura AppFuse. |
At line 15 changed 5 lines. |
!Table of Contents |
* [1] Create a new ManagerTest to run JUnit tests on the Manager |
* [2] Create a new Manager to talk to the DAO |
* [3] Configure Spring for this Manager and Transactions |
* [4] Run the ManagerTest |
!Tabela de Conteúdo |
* [1] Criar um novo ManagerTest para rodar testes JUnit no Manager |
* [2] Criar um novo Manager para conversar com o DAO |
* [3] Configurar o Spring para este Manager e as Transações |
* [4] Rodar o ManagerTest |
At line 21 changed 2 lines. |
!!Create a new ManagerTest to run JUnit tests on the Manager [#1] |
In [Part I|CreateDAO], we created a Person object and PersonDao - so let's continue developing this entity. First, let's create a JUnit test for the PersonManager. Create PersonManagerTest in the test/service/**/service directory. We'll want to test the same basic methods (get, save, remove) that our DAO has. |
!!Criar um novo ManagerTest para rodar testes JUnit no Manager [#1] |
Na [Parte I|CreateDAO_pt], criamos o objeto Person e o PersonDao - então vamos continuar a desenvolver esta entidade. Primeiramente, vamos criar o teste JUnit para o PersonManager. Crie a classe PersonManagerTest no diretório test/service/**/service. Queremos testar os mesmos métodos básicos (get, save, remove) que o nosso DAO tem. |
At line 24 changed 1 line. |
;:''This may seem redundant (why all the tests!), but these tests are GREAT to have 6 months down the road.'' |
;:''Isto pode parecer redundante (o porquê de todos estes testes!), mas estes testes são EXCELENTES para ter depois de 6 meses de estrada.'' |
At line 26 changed 1 line. |
This class should extend [BaseManagerTestCase|http://raibledesigns.com/downloads/appfuse/api/org/appfuse/service/BaseManagerTestCase.java.html], which already exists in the ''service'' package. The parent class (BaseManagerTestCase) serves the same functionality as the BaseDaoTestCase - to load a properties file that has the same name as your *Test.class, as well as to initialize Spring's ApplicationContext. |
Esta classe deve estender [BaseManagerTestCase|http://raibledesigns.com/downloads/appfuse/api/org/appfuse/service/BaseManagerTestCase.java.html], que já existe no pacote ''service''. A classe pai (BaseManagerTestCase) serve para o mesmo propósito da classe BaseDaoTestCase - carregar um arquivo .properties que possui o mesmo nome de sua classe *Test, assim como inicializar o ApplicationContext do Spring. |
At line 28 changed 1 line. |
;:%%(color: blue)''I usually copy (open → save as) an existing test (i.e. UserManagerTest.java) and find/replace [[Uu]ser with [[Pp]erson, or whatever the name of my object is.''%% |
;:%%(color: blue)''Usualmente eu copio (open → save as) um teste existente (i.e. UserManagerTest.java) e utilizando ctrl+f para encontrar/substituir [[Uu]ser com [[Pp]erson, ou qualquer que seja o nome do meu objeto.''%% |
At line 30 changed 1 line. |
The code below is what we need for a basic JUnit test of our Managers. The code below simply creates and destroys the PersonManager. The "ctx" object is initialized in the [BaseManagerTestCase|http://raibledesigns.com/downloads/appfuse/api/org/appfuse/service/BaseManagerTestCase.java.html] class. |
O código abaixo é o que precisamos para um teste JUnit básico para nossos Managers. Diferente do DaoTest, este teste utiliza [jMock|http://jmock.org] para isolar o Manager de suas dependências e fazer um teste unitário ''verdadeiro''. |
At line 36 changed 2 lines. |
import org.apache.commons.logging.Log; |
import org.apache.commons.logging.LogFactory; |
import java.util.List; |
import java.util.ArrayList; |
At line 38 added 1 line. |
import org.appfuse.dao.PersonDao; |
At line 40 added 1 line. |
import org.appfuse.service.impl.PersonManagerImpl; |
At line 42 added 2 lines. |
import org.jmock.Mock; |
import org.springframework.orm.ObjectRetrievalFailureException; |
At line 46 added 4 lines. |
private final String personId = "1"; |
private PersonManager personManager = new PersonManagerImpl(); |
private Mock personDao = null; |
private Person person = null; |
At line 44 changed 6 lines. |
private Person person; |
private PersonManager mgr = null; |
private Log log = LogFactory.getLog(PersonManagerTest.class); |
|
protected void setUp() { |
mgr = (PersonManager) ctx.getBean("personManager"); |
protected void setUp() throws Exception { |
super.setUp(); |
personDao = new Mock(PersonDao.class); |
personManager.setPersonDao((PersonDao) personDao.proxy()); |
At line 52 changed 2 lines. |
protected void tearDown() { |
mgr = null; |
protected void tearDown() throws Exception { |
super.tearDown(); |
personManager = null; |
At line 55 removed 4 lines. |
|
public static void main(String[] args) { |
junit.textui.TestRunner.run(PersonManagerTest.class); |
} |
At line 63 added 1 line. |
Agora que temos o framework JUnit configurado para esta classe, vamos ao que interessa: os métodos de teste para nos certificar que tudo funciona no nosso Manager. Aqui está um exemplo do [Tutorial DAO|CreateDAO_pt] para ajudá-lo a entender o que estamos fazendo. |
At line 62 changed 1 line. |
Now that we have the JUnit framework down for this class, let's add the meat: the test methods to make sure everything works in our Manager. Here's a snippet from the [DAO Tutorial|CreateDAO] tutorial to help you understand what we're about to do. |
;:''...criamos métodos que começam com "test" (tudo em letra minúscula). Enquanto estes métodos forem públicos, possuírem um retorno void e não possuírem argumentos, serão chamados pela nossa task <junit> em nosso arquivo build.xml do Ant. Aqui estão alguns testes simples para testar operações CRUD. Uma coisa importante para relembrar é que cada método (conhecido como teste), deve ser autônomo.'' |
At line 64 changed 1 line. |
;:''...we create methods that begin with "test" (all lower case). As long as these methods are public, have a void return type and take no arguments, they will be called by our <junit> task in our Ant build.xml file. Here's some simple tests for testing CRUD. An important thing to remember is that each method (also known as a test), should be autonomous.'' |
Adicione os métodos seguintes ao seu arquivo PersonManagerTest.java: |
At line 66 removed 2 lines. |
Add the following methods to your PersonManagerTest.java file: |
|
At line 71 changed 4 lines. |
person = (Person) mgr.getPerson("1"); |
|
assertTrue("person.firstName not null", |
person.getFirstName() != null); |
// set expected behavior on dao |
personDao.expects(once()).method("getPerson") |
.will(returnValue(new Person())); |
person = personManager.getPerson(personId); |
assertTrue(person != null); |
personDao.verify(); |
At line 78 changed 3 lines. |
person = (Person) mgr.getPerson("1"); |
String name = person.getFirstName(); |
person.setFirstName("test"); |
// set expected behavior on dao |
personDao.expects(once()).method("savePerson") |
.with(same(person)).isVoid(); |
At line 82 changed 2 lines. |
person = (Person) mgr.savePerson(person); |
assertTrue("name updated", person.getFirstName().equals("test")); |
personManager.savePerson(person); |
personDao.verify(); |
} |
At line 85 removed 4 lines. |
person.setFirstName(name); |
mgr.savePerson(person); |
} |
|
At line 91 removed 1 line. |
person = (Person) populate(person); |
At line 93 changed 3 lines. |
person = (Person) mgr.savePerson(person); |
assertTrue(person.getFirstName().equals("Bill")); |
assertTrue(person.getId() != null); |
// set required fields |
person.setFirstName("firstName"); |
person.setLastName("lastName"); |
At line 97 changed 4 lines. |
if (log.isDebugEnabled()) { |
log.debug("removing person, personId: " + |
person.getId()); |
} |
// set expected behavior on dao |
personDao.expects(once()).method("savePerson") |
.with(same(person)).isVoid(); |
personManager.savePerson(person); |
personDao.verify(); |
At line 102 changed 1 line. |
mgr.removePerson(person.getId().toString()); |
// reset expectations |
personDao.reset(); |
At line 104 changed 1 line. |
assertNull(mgr.getPerson(person.getId().toString())); |
personDao.expects(once()).method("removePerson").with(eq(new Long(personId))); |
personManager.removePerson(personId); |
personDao.verify(); |
|
// reset expectations |
personDao.reset(); |
// remove |
Exception ex = new ObjectRetrievalFailureException(Person.class, person.getId()); |
personDao.expects(once()).method("removePerson").isVoid(); |
personDao.expects(once()).method("getPerson").will(throwException(ex)); |
personManager.removePerson(personId); |
try { |
personManager.getPerson(personId); |
fail("Person with identifier '" + personId + "' found in database"); |
} catch (ObjectRetrievalFailureException e) { |
assertNotNull(e.getMessage()); |
} |
personDao.verify(); |
At line 108 changed 1 line. |
This class won't compile at this point because we have not created our PersonManager interface. |
Esta classe não compilará neste ponto porque nós não criamos nossa interface PersonManager. |
At line 110 changed 1 line. |
;:%%(color: blue)''I think it's funny how I've followed so many patterns to allow __extendibility__ in AppFuse. In reality, on most projects I've been on - I learn so much in a year that I don't want to extend the architecture - I want to rewrite it. Hopefully by keeping AppFuse up to date with my perceived best practices, this won't happen as much. Each year will just be an upgrade to the latest AppFuse, rather than a re-write. ;-)'' |
;:%%(color: blue)''Acho engraçado como segui tantos padrões para permitir __extendibilidade__ no AppFuse. Na realidade, na maioria dos projetos que participei, aprendi tanto em um ano que não queria extender a arquitetura - queria reescrevê-la. Espero que isto não ocorra tanto, utilizando minhas melhores práticas para manter o AppFuse em dia. A cada ano haverá apenas um upgrade para uma nova versão do AppFuse, ao invés de ter de reescrevê-lo. ;-)'' |
At line 112 changed 1 line. |
!!Create a new Manager to talk to the DAO [#2] |
!!Criar um novo Manager para conversar com o DAO [#2] |
At line 114 changed 1 line. |
First off, create a PersonManager.java interface in the src/service/**/service directory and specify the basic CRUD methods for any implementation classes. ''I've eliminated the JavaDocs in the class below for display purposes.'' |
Primeiramente, crie uma interface PersonManager.java no diretório src/service/**/service, e especifique os métodos CRUD básicos para implementações posteriores. ''Eliminei os JavaDocs na classe abaixo para não poluir o código.'' |
At line 116 changed 1 line. |
;:%%(color: blue)''As usual, I usually duplicate (open → save as) an existing file (i.e. UserManager.java).''%% |
;:%%(color: blue)''Como sempre, duplico (open → save as) um arquivo existente (i.e. UserManager.java).''%% |
At line 141 added 1 line. |
import org.appfuse.dao.PersonDao; |
At line 124 removed 2 lines. |
import java.util.List; |
|
At line 127 changed 3 lines. |
|
public List getPeople(Person person); |
|
public void setPersonDao(PersonDao dao); |
At line 131 changed 3 lines. |
|
public Person savePerson(Object person); |
|
public void savePerson(Person person); |
At line 138 changed 1 line. |
Now let's create a PersonManagerImpl class that implements the methods in PersonManager. To do this, create a new class in src/service/**/service and name it PersonManagerImpl.java. It should extend BaseManager and implement PersonManager. |
Agora vamos criar uma classe PersonManagerImpl que implementa os métodos da interface PersonManager. Para fazer isto, crie uma nova classe em src/service/**/service com o nome PersonManagerImpl.java. Esta classe deve estender BaseManager e implementar PersonManager. |
At line 142 changed 1 line. |
package org.appfuse.service; |
package org.appfuse.service.impl; |
At line 144 removed 3 lines. |
import org.apache.commons.logging.Log; |
import org.apache.commons.logging.LogFactory; |
|
At line 159 added 1 line. |
import org.appfuse.service.PersonManager; |
At line 150 removed 6 lines. |
import java.util.List; |
|
/** |
* @author mraible |
* @version $Revision: $ $Date: May 25, 2004 11:46:54 PM $ |
*/ |
At line 157 removed 1 line. |
private static Log log = LogFactory.getLog(PersonManagerImpl.class); |
At line 164 removed 4 lines. |
public List getPeople(Person person) { |
return dao.getPeople(person); |
} |
|
At line 172 changed 1 line. |
public Person savePerson(Object person) { |
public void savePerson(Person person) { |
At line 174 removed 1 line. |
return (Person) person; |
At line 183 changed 2 lines. |
One thing to note is the {{setPersonDao}} method. This is used by Spring to bind the PersonDao to this Manager. This is configured in the applicationContext-service.xml file. We'll get to configuring that in Step 3[3]. |
You should be able to compile everything now using "ant compile-service"... |
Uma coisa que deve ser notada é o método {{setPersonDao}}. Este método é utilizado pelo Spring para ligar o PersonDao a este Gerente(Manager). Isto é configurado no arquivo applicationContext-service.xml. Veremos como se configura este arquivo no Passo 3[3]. |
Já nos é possível compilar tudo agora utilizando "ant compile-service"... |
At line 186 changed 1 line. |
Finally, we need to create the PersonManagerTest.properties file in test/service/**/service so that {{person = (Person) populate(person);}} will work in our test. |
Agora necessitamos editar o arquivo de configuração do Spring para nossa camada Service para lhe dizer da existência do novo Manager. |
At line 188 changed 1 line. |
;:''Duplicating (and renaming) the PersonDaoTest.properties is the easy route. Alternatively, you could set the properties manually.'' |
!!Configurar o Spring para este Manager e as Transações [#3] |
At line 190 changed 2 lines. |
{{{firstName=Bill |
lastName=Joy}}} |
Para notificar o Spring da nossa interface PersonManager e sua implementação, abra o arquivo src/service/**/service/applicationContext-service.xml. Nele, veremos uma configuração comentada para o bean "PersonManager". Retire o comentário, ou adicione o seguinte código ao final do arquivo. |
At line 193 removed 6 lines. |
Now we need to edit Spring's config file for our services layer so it will know about this new Manager. |
|
!!Configure Spring for this Manager and Transactions [#3] |
|
To notify Spring of this our PersonManager interface and it's implementation, open the src/service/**/service/applicationContext-service.xml file. In here, you will see an existing configuration for the UserManager. You should be able to copy that and change a few things to get the XML fragment below. Add the following to the bottom of this file. |
|
At line 201 changed 3 lines. |
<!-- Person Manager --> |
<bean id="personManagerTarget" class="org.appfuse.service.PersonManagerImpl" singleton="false"> |
<property name="personDao"><ref bean="personDAO"/></property> |
<bean id="personManager" parent="txProxyTemplate"> |
<property name="target"> |
<bean class="org.appfuse.service.impl.PersonManagerImpl" autowire="byName"/> |
</property> |
At line 205 removed 8 lines. |
|
<!-- Transaction declarations for business services. To apply a generic transaction proxy to |
all managers, you might look into using the BeanNameAutoProxyCreator --> |
<bean id="personManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> |
<property name="transactionManager"><ref bean="transactionManager"/></property> |
<property name="target"><ref local="personManagerTarget"/></property> |
<property name="transactionAttributeSource"><ref local="defaultTxAttributes"/></property> |
</bean> |
At line 215 changed 1 line. |
''Note: [I|DaveKeller] had SAX throw an error because defaultTxAttributes was not defined so I replaced the transactionAttributeSource with this instead, which allowed my unit test to run successfully.'' |
O atributo "parent" se refere a uma definição de bean para um [TransactionProxyFactoryBean|http://www.springframework.org/docs/api/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.html], que possui todos os atributos de transação básicos inicializados. |
At line 217 changed 1 line. |
[{Java2HtmlPlugin |
!!Rodar o ManagerTest [#4] |
At line 219 changed 5 lines. |
<property name="transactionAttributes"> |
<props> |
<prop key="*">PROPAGATION_REQUIRED</prop> |
</props> |
</property> |
Salve todos os seus arquivos editados e tente rodar "ant test-service -Dtestcase=PersonManager" mais uma vez. |
At line 225 removed 7 lines. |
}] |
|
|
!!Run the ManagerTest [#4] |
|
Save all your edited files and try running "ant test-service -Dtestcase=PersonManager" one more time. |
|
At line 238 changed 1 line. |
''Next Up:'' __Part III:__ [Creating Actions and JSPs|CreateActions] - A HowTo for creating Actions and JSPs in the AppFuse architecture. |
Os arquivos modificados e adicionados nesta parte [podem ser baixados aqui|http://appfuse.dev.java.net/files/documents/1397/7484/appfuse-tutorial-managers-1.6.zip]. |
|
''Próxima:'' __Parte III:__ [Criando Actions e JSPs|CreateActions_pt] - Um tutorial para criação de Actions e JSPs na arquitetura AppFuse. |