Spring, como é sabido pelos desenvolvedores Java, é um framework escrito na linguagem Java e muito poderoso, estável e cujas funcionalidades auxiliam os programadores a escrever menos código.
Assim, como é possível fazer programas desenvolvidos em Scala interoperarem com programas desenvolvidos em Java, então também é possível construir uma aplicação em Scala totalmente integrada com Spring. Para tanto existem algumas observações, que precisam ser consideradas. O objetivo aqui não é discutir se isto é ou não viável, mas sim como fazer isto de forma simples e mantendo a experiência do desenvolvedor.
Para começar vamos considerar uma Entidade chamada de Order que deverá ser mantida em um banco de dados PostgreSQL. Supondo que você tenha um Projeto Scala preparado com todas as bibliotecas e dependências do Spring (cujos JARs estão em lib), o arquivo de configuração terá o seguinte conteúdo:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<!-- DataSource -->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost:5432/db_scala" />
<property name="username" value="postgres" />
<property name="password" value="123456" />
</bean>
<!-- Hibernate Configuration-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="datasource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.use_sql_comments">false</prop>
</props>
</property>
<property name="annotatedClasses">
<array>
<value>...</value>
</array>
</property>
</bean>
<!-- Transaction management -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" >
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- Instruct Spring to perform declarative transaction management automatically on annotated classes. -->
<tx:annotation-driven transaction-manager="transactionManager" mode="proxy" />
</beans>
O objeto que repesentará o pedido "Order" terá uma identidade que em outro momento utilizaremos como EmbededId. Por enquanto basta termos:
/**
* Representa a identidade de uma entidade
*/
case class Identity(companyId:String, domObjId: String)
/**
* Pedido
*/
@Entity
case class Order(identity:Identity){
@Id
private var id = identity.domObjId
}
Para realizar as operações de CRUD, construíremos um DAO (Data Access Object pattern), o qual terá o seguinte conteúdo:
class OrderRepositoryImpl(sf:SessionFactory) {
private val hibernate:HibernateTemplate = new HibernateTemplate(sf)
def save(obj:Order){
hibernate.saveOrUpdate(obj)
}
def remove(obj:Order){
hibernate.execute(new HibernateCallback[Unit](){
override def doInHibernate(session:Session) = {
val query = session.createQuery("delete from Order o where o.companyid = :companyid and o.code = :code")
query.setParameter("companyid", obj.identity.companyId)
query.setParameter("code", obj.identity.domObjId)
query.executeUpdate()
}
})
}
def removeAll(){
hibernate.execute(new HibernateCallback[Unit](){
override def doInHibernate(session:Session){
val query = session.createQuery("delete from Order")
query.executeUpdate()
}
})
}
def find(id:Identity):Order = {
hibernate.execute(new HibernateCallback[Order](){
override def doInHibernate(session:Session):Order = {
val query = session.createQuery("select o from Order o where o.companyid = :companyid and o.code = :code")
query.setParameter("companyid", id.companyId)
query.setParameter("code", id.domObjId)
query.uniqueResult().asInstanceOf[Order]
}
})
}
def findByCriteria(criteria: Criteria):List[Order] = {
import scala.collection.JavaConversions._
hibernate.execute(new HibernateCallback[List[Order]](){
override def doInHibernate(session:Session):List[Order] = {
val c = DetachedCriteria
.forClass(Order.getClass())
.getExecutableCriteria(session)
c.list().asInstanceOf[List[Order]]
}
})
}
}
Algumas observações, aqui já para iniciar:
1) Scala possui Collections próprias, sendo que o Spring trabalho com as Collections do Java. Para compatibilizar é necessário importar a seguinte biblioteca:
import scala.collection.JavaConversions._
2) O session factory será passado pelo construtor no Spring, portanto a :
class OrderRepositoryImpl(sf:SessionFactory) {...
Com estas operações definidas será possível realizar o nosso CRUD sobre Order utilizando OrderRepositoryImpl cujo SessionFactory será injetado pelo Spring. Mas não para por ai. Para realizar os testes será utilizado o ScalaTest Framework. E o conteúdo do caso de teste será:
class OrderTest extends FunSuite {
var i = new Identity("co0001", "or0001")
var o = new Order(i)
test ("salvar o pedido") {
//na pasta src dentro de META-INF, encontra-se o arquivo de configuração do spring
val context = new ClassPathXmlApplicationContext("META-INF/ag-shoppingmanager-core.xml")
val bean = context.getBean("orderRepository")
//verificação antes da remoção
bean.asInstanceOf[OrderRepositoryImpl].save(o)
//verificação antes da remoção
val e = bean.asInstanceOf[OrderRepositoryImpl].find(i)
//teste
assert(e != null)
}
...
}
object OrderTestMain {
def main(args: Array[String]) {
Runner.main(Array[String]("-p", "scalatest-1.7.2-tests.jar", "-o", "-s", "br.com.arigarcia.shoppingmanager.core.OrderTest"));
}
}
Pronto... realizado o teste você poderá verificar que os testes se sairão perfeitamente. Mas como foi dito no inicio segue algumas observações:
1) A utilização de List (do Scala) poderá criar uma complexidade sem nenhuma vantagem;
2) As anotações de @Id e @Column só podem ser feitas diretamente nos atributos e não em parametros, logo não será possível utilizar um construtor personalizado para a entidade no Scala de uma forma mais elegante, sendo necessário a redundância de dados, ou, se optar por remover o construtor personalizado, construir um "ScalaBean" (similar ao JavaBean, para a linguagem Java).
3) Os atributos que possuem Enumerations devem ser repensados para serem convertidos em String manualmente ou então terá que construir um conversor implicito personalizado para resolver este problema.
Por enquanto é só! Com certeza terá mais problemas... a questão é avaliar qual o benefício de interoperar estas aplicações:
- O que o Scala faz que Java não faz?
- Em que realmente o Scala é melhor que Java?
- Compensa estudar tanta complexidade para simplesmente utilizar Scala ou é melhor utilizar Java?
Existem outros problemas no que se refere a produtividade, não por causa da linguagem em si, mas por causa da falta de ferramente que auxilie a encontrar problemas mais rapidamente (o eclipse ainda está muito imaturo, mas é o mais maduro dentre os que testes - netbeans, intelij, emacs, e mais outras 2 que não lembro o nome agora).
Até o final desse projeto passarei mais informações sobre desenvolvimento com Scala.
Até lá!