Spring MVC, aplicació web

L’arquitectura i el procés d’Spring MVC respecte a una petició des d’un navegador és la que es mostra a la figura.

Figura Arquitectura i procés d’una aplicació Spring MVC

La petició feta des d’un navegador client és recollida pel Front Controller (Dispatcher Servlet) i la passa al controlador adient. Aquest construeix el model i el retorna (amb l’estat que correspongui) al Dispatcher Servlet que retornarà la resposta, determinant la vista a retornar a partir del View Resolver assignat i amb els valors del model.

Veurem com el controlador construeix el model, és a dir, la part de negoci de la vostra aplicació web (enterprise-level).

Amb Spring MVC podeu fer servir una bona pràctica per a aquest tipus de desenvolupament, consistent a estructurar el codi en capes (layers) i donant reusabilitat i baix acoblament a la vostra aplicació.

Les capes que es recomanen són quatre:

  • presentació (Presentation)
  • domini (Domain)
  • serveis (Services)
  • persistència (Persist)

El diagrama de la figura mostra aquestes capes i les seves interaccions.

Figura Capes i interaccions d’una aplicació Spring MVC

Els objectes vistos fins ara, com Dispatcher Servlet, controladors, View Resolvers i d’altres conformen la capa de presentació.

La capa de persistència és la que conté els objectes que interaccionen amb les dades; per exemple, obtenint un conjunt de registres d’una base de dades a partir d’una consulta SQL.

Un controlador podria demanar directament les dades a la capa de persistència, però a l’arquitectura del diagrama es proposa la creació de la capa de servei per tal de poder aplicar les regles de negoci amb i/o abans d’obtenir les dades. Per exemple, l’aplicació d’una restricció no implementada a la base de dades, com no deixar a un client fer una comanda si el seu deute és superior a cert import.

En tot cas, les dades que s’obtenen o s’estan construint es mapegen sobre els objectes de la capa de domini; per exemple, objectes de classes entitat que poden representar una taula d’una base de dades.

Continuareu amb el vostre projecte d’Estoc de medicaments (“stmedioc”) desenvolupant la resta de capes i descrivint els conceptes i la metodologia implicades en cadascuna d’elles.

Estoc de medicaments, capa de domini

La capa domini d’una aplicació web consisteix en la implementació de les classes d’un o més models de domini.

El model de domini és la representació, per exemple amb un diagrama UML, de les classes corresponents a les dades del problema a resoldre de la lògica de negoci.

En el cas de l’aplicació d’Estoc de medicaments, dissenyada amb propòsits pedagògics, només hi ha una entitat Medicament i, per tant, la capa de domini només tindrà una classe, que anomenareu Medicament (vegeu la figura).

Figura Classe Medicament

Un entitat Medicament podria contenir més propietats i mètodes, però de moment només fareu servir aquest a la vostra aplicació. El nom de les propietats és suficient per relacionar-les amb els conceptes que representen. No obstant això, cal fer algunes apreciacions:

  • medicamentID és el codi de medicament.
  • producer és el proveïdor.
  • stockQuantity són les unitats emmagatzemades i stockInOrder són les unitats que estan demanades al proveïdor però encara no heu introduït al magatzem.
  • active és per indicar si un medicament es fa servir (valor true) o s’ha donat de baixa (valor false).

Els objectes creats amb classes de domini s’acostumen a dir objectes de domini (Domain Object).

Creació del domini

Com que el nostre domini està format unicament per la classe Medicament, al projecte “stmedioc” heu de crear un paquet ioc.xtec.cat.domain i la classe Medicament amb el contingut que es mostra a continuació:

El codi del projecte “stmedioc” en l’estat de capa de domini es pot descarregar des de l’enllaç que trobareu als annexos de la unitat.

Però per seguir el desenvolupament de la capa de domini és millor partir del que ja heu fet servir per a Estoc de medicaments. Benvinguda i copiar-lo amb el nom de “stmedioc201”.

package cat.xtec.ioc.domain;

public class Medicament {

    private String medicamentId;
    private String name;
    private double price;
    private String description;
    private String producer;
    private String category;
    private long stockQuantity;
    private long stockInOrder;
    private boolean active;

    public Medicament() {
        super();
    }

    public Medicament(String medicamentId, String name, double price) {
        this.medicamentId = medicamentId;
        this.name = name;
        this.price = price;
    }

    //Heu d'afegir els getters i setters de totes les propietats
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Medicament other = (Medicament) obj;
        if (medicamentId == null) {
            if (other.medicamentId != null) {
                return false;
            }
        } else if (!medicamentId.equals(other.medicamentId)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((medicamentId == null) ? 0 : medicamentId.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "Medicament [codi=" + medicamentId + ", nom=" + name + "]";
    }
}

Fent servir el domini

Penseu ara en la necessitat de mostrar per al navegador un medicament mitjançant una petició GET des d’un URL.

Davant d’una petició, el Dispatcher Servlet delegarà a un controlador la petició i aquest s’encarregarà de construir el model.

A Spring MVC, amb l’estructura de capes que esteu desenvolupant, implementareu un nou controlador MedicamentController que agafi aquesta petició i construeixi el model a partir del domini (de la classe Medicament).

Heu de crear la classe MedicamentController dins del paquet cat.xtec.ioc.controller amb el contingut que es mostra a continuació.

package cat.xtec.ioc.controller;

import cat.xtec.ioc.domain.Medicament;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MedicamentController {

    @RequestMapping(value = "/medicaments", method = RequestMethod.GET)
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ModelAndView modelview = new ModelAndView("medicaments");
        Medicament ibuprofe = new Medicament("M010", "Ibuprofé", 2);
        ibuprofe.setDescription("Ibuprofé de 600mg");
        ibuprofe.setCategory("Anti-inflamatori");
        ibuprofe.setProducer("Cinfa");
        ibuprofe.setStockQuantity(214);
        modelview.getModelMap().addAttribute("medicament", ibuprofe);
        return modelview;
    }
}

Cal destacar que al controlador MedicamentController esteu donant valors a l’entitat, i en el model de capes això serà responsabilitat d’altres. En aquest cas, doneu el valors en el controlador perquè encara no heu desenvolupat les altres capes, però després canviareu aquesta classe per fer-ho on toca.

Fixeu-vos que fins ara els atributs que heu afegit al model eren simples, com string. En el cas de MedicamentController esteu creant un atribut que és un objecte: un domain object Medicament.

MedicamentController retorna un ModelAndView amb nom medicaments. Segons la configuració que havíeu fet per al View Resolver a DispatcherServlet-servlet.xml, Spring cercarà la vista WEB-INF/views/medicaments.jsp per mostrar-la i afegirà l’atribut medicament (objecte Medicament) a la resposta.

Heu de crear aquesta vista a la carpeta esmentada amb el contingut que es mostra a continuació.

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ca">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
        <title>Medicaments</title>
    </head>
    <body>
        <section>
            <div class="jumbotron">
                <div class="container">
                    <h1>Medicaments</h1>
                    <p>Llista de medicaments en magatzem</p>
                </div>
            </div>
        </section>
        <section class="container">
            <div class="row">
                <div class="col-sm-6 col-md-3" style="padding-bottom: 15px">
                    <div class="thumbnail">
                        <div class="caption">
                            <h3>${medicament.name}</h3>
                            <p>${medicament.description}</p>
                            <p>${medicament.price} €</p>
                            <p>Hi ha ${medicament.stockQuantity} unitats en magatzem</p>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </body>
</html>

Fixeu-vos que esteu servir les expressions tipus EL (Expression Language) ${atribut.propietat} i que com a atribut havíeu passat un objecte Medicament, i per tant podeu usar les seves propietats. Tingueu en compte que, per exemple, amb ${medicament.name} s’executarà el mètode medicament.getName(), i si no l’heu implementat (getters and setters) llavors el resultat serà null.

Si executeu l’aplicació i afegiu a l’URL del navegador /medicaments podreu veure el que es mostra en la figura.

Figura Sortida de l’aplicació Estoc de medicaments, domini

Partíeu de la capa de presentació feta, però anireu afegint controladors i vistes a mesura que les necessiteu.

La capa de domini es correspon pràcticament amb les classes Entitat del problema a resoldre, i en el cas de l’aplicació Estoc de medicaments és la classe Medicament. Però heu fet trampes posant valors en el controlador MedicamentController amb la finalitat de provar. I això s’ha de resoldre continuant amb el desenvolupament de les següents capes.

Estoc de medicaments, capa de persistència

La capa de persistència és el conjunt d’objectes que interaccionen amb les dades. Les dades poden estar emmagatzemades en bases de dades i en altres tipus de formats, com XML, JSON, imatges, vídeo, etc.

La capa de persistència conté objectes repositori (Repository Objects) que permeten mapejar les dades de la font de dades (base de dades o no) amb els objectes de domini (Domain Object). Són, en realitat, els responsables de les operacions CRUD (Create, Read, Update i Delete).

En operacions d’obtenció de dades, els objectes repositori consulten la font de dades, per exemple via SQL, i el resultat és mapejat sobre objectes de domini.

En operacions d’actualització de les dades, els objectes repositori parteixen de l’estat dels objectes de domini i amb els seus valors actualitzen la font de dades.

Per indicar a Spring que una classe és un repositori es fa servir l’anotació @Repository (org.springframework.stereotype.Repository). Aquesta anotació també permet que les excepcions SQL es converteixin en DataAccessExceptions de Spring.

Amb aquests conceptes descrits ja podeu crear la capa de persistència de la vostra aplicació Estoc de medicaments.

Al desenvolupament de la capa de domini heu mostrat un únic medicament que, a més, temporalment, es crea directament al controlador. Mostrareu una llista de medicaments i a més traureu la creació del medicament del controlador.

La manera d’accedir a les dades està fora de l’abast d’aquesta unitat, i per això fareu servir objectes en memòria com si fossin la base de dades. Només haureu de canviar aquests objectes per d’altres que realment es connectin i dialoguin amb la base de dades si voleu dotar de veritable persistència l’Estoc de medicaments.

Creareu una interfície MedicamentRepository i la implementareu amb la classe InMemoryMedicamentRepository amb els mètodes per mostrar la llista de medicaments, només un mètode en aquest cas. Per altra banda, fareu que el controlador dialogui directament amb aquesta capa de persistència, encara que ho canviareu més endavant per tal que dialogui amb la capa de servei.

Feu servir una interfície, perquè això us permet que el controlador declari i cridi la interfície, i d’aquesta manera podeu canviar la implementació sense canviar res més.

Creació de la persistència

Afegiu el paquet cat.ioc.xtec.domain.repository al nostre projecte “stmedioc”, i en aquest paquet una nova interfície MedicamentRepository amb el contingut següent:

package cat.xtec.ioc.domain.repository;
 
import cat.xtec.ioc.domain.Medicament;
import java.util.List;
 
public interface MedicamentRepository {
  List <Medicament> getAllMedicaments();
}

El codi del projecte “stmedioc” en l’estat de capa de persistència es pot descarregar des de l’enllaç que trobareu als annexos de la unitat.

Però per seguir el desenvolupament de la capa de persistència és millor partir del que ja heu fet servir per a Estoc de medicaments, capa de domini, i copiar-lo amb el nom de “stmedioc202”.

Afegiu el paquet cat.ioc.xtec.domain.repository.impl al nostre projecte “stmedioc”. També us interessa que Spring cerqui automàticament les classes d’aquest paquet per poder injectar els objectes quan es trobi una anotació d’aquest tipus. Per això, canvieu la propietat context:component-scan de DispatcherServlet-servlet.xml:

<context:component-scan base-package="cat.xtec.ioc.controller cat.xtec.ioc.domain.repository" />

En el paquet cat.ioc.xtec.domain.repository.impl, la classe InMemoryMedicamentRepository implementa MedicamentRepository amb el següent contingut:

package cat.xtec.ioc.domain.repository.impl;
 
import cat.xtec.ioc.domain.Medicament;
import cat.xtec.ioc.domain.repository.MedicamentRepository;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Repository;
 
@Repository
public class InMemoryMedicamentRepository implements MedicamentRepository {
 
	private List<Medicament> listOfMedicaments = new ArrayList<Medicament>();
 
	public InMemoryMedicamentRepository() {
    	Medicament ibuprofe = new Medicament("M010", "Ibuprofé", 2);
        ibuprofe.setDescription("Ibuprofé de 600mg");
        ibuprofe.setCategory("Anti-inflamatori");
        ibuprofe.setProducer("Cinfa");
        ibuprofe.setStockQuantity(214);
 
    	Medicament paracetamol = new Medicament("M020", "Paracetamol", 2.6);
        paracetamol.setDescription("Paracetamol 1g");
        paracetamol.setCategory("Analgèsic");
        paracetamol.setProducer("Ferrer");
        paracetamol.setStockQuantity(56);
 
    	Medicament acacetilsalicilico = new Medicament("M030", "Ac Acetil Salicílico", 2.6);
        acacetilsalicilico.setDescription("Ac Acetil Salicílico");
        acacetilsalicilico.setCategory("Analgèsic");
        acacetilsalicilico.setProducer("Bayer");
        acacetilsalicilico.setStockQuantity(15);    	
    	
        listOfMedicaments.add(ibuprofe);
        listOfMedicaments.add(paracetamol);
        listOfMedicaments.add(acacetilsalicilico);
	}
 
	public List<Medicament> getAllMedicaments() {
    	return listOfMedicaments;
	}
}

Aquesta classe repository hauria de connectar amb la font de dades, però com que és fora de l’abast d’aquesta unitat feu que les dades resideixin en memòria creant-les en el constructor. Per això, getAllMedicaments retorna la llista de medicaments propietat de la classe, però en realitat hauria d’anar a buscar-la a la font de dades.

Canvieu el controlador MedicamentController per obtenir la llista de medicaments des de la capa de persistència. El contingut de MedicamentController sense els imports és com es mostra a continuació:

@Controller
public class MedicamentController {
 
	@Autowired
	private MedicamentRepository medicamentRepository;
	
	@RequestMapping(value = "/medicaments", method = RequestMethod.GET)
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        	throws ServletException, IOException {
    	ModelAndView modelview = new ModelAndView("medicaments");    	
        modelview.getModelMap().addAttribute("medicaments", medicamentRepository.getAllMedicaments());
    	return modelview;
	}
}

Haureu d’afegir-hi els imports:

import org.springframework.beans.factory.annotation.Autowired;
import cat.xtec.ioc.domain.repository.MedicamentRepository;

Hi podeu suprimir l’import:

import cat.xtec.ioc.domain.Medicament;)

S’ha creat la propietat medicamentRepository que, mitjançant l’anotació @Autowired, serà inicialitzada per Spring amb l’objecte de tipus MedicamentRespository del Web Application Context.

S’ha canviat la creació d’un únic medicament en la mateixa classe controlador per a la crida a la llista de tots els medicaments que us proporciona la persistència. Noteu que s’ha canviat l’atribut medicament per medicaments.

Fixeu-vos com no es crida directament la classe InMemoryMedicamentRepository, ja que es declara i es crida la interfície. Això us permetrà canvis en la font de dades i en la manera de dialogar amb ella sense tocar el controlador.

Només us queda mostrar la llista dels medicaments que hi ha a la vostra font de dades (creats en memòria). Canvieu la vista medicaments.jsp: heu d’afegir al principi de tot la declaració del taglib i heu de canviar tot el contingut de la div amb classe=”row” pel codi que es mostra a continuació.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
                <c:forEach items="${medicaments}" var="medicament">
                    <div class="col-sm-6 col-md-3" style="padding-bottom: 15px">
                        <div class="thumbnail">
                            <div class="caption">
                                <h3>${medicament.name}</h3>
                                <p>${medicament.description}</p>
                                <p>${medicament.price}</p>
                                <p>Hi ha ${medicament.stockQuantity} unitats en magatzem</p>
                            </div>
                        </div>
                    </div>
                </c:forEach>

Executeu l’aplicació i afegiu /medicaments a l’URL. Heu d’obtenir un resultat similar al de la figura.

Figura Sortida de l’aplicació Estoc de medicaments, persistència

L’URL /medicaments ha fet que el Dispatcher Servlet passi la petició a MedicamentController. En primer lloc, demana la injecció d’un objecte MedicamentRepository (anotació @Autowired) i Spring retorna un objecte de la implementació InMemoryMedicamentRepository.

Recordeu que feu servir una classe que construeix valors ficticis en memòria perquè l’accés a dades no és l’objectiu d’aquesta unitat, però podríeu fer una classe que retornarà els valors des d’una base de dades, per exemple MedicamentDAO, que implementaria MedicamentRepository i no hauríeu de canviar MedicamentController. Això és l’acoblament feble entre capes.

En segon lloc, es construeix el ModelAndView a partir del repositori, demanant tots els medicaments al repositori i passant-los a la vista Medicaments sobre l’atribut del mateix nom.

Finalment, es mostra la vista que, amb etiquetes de JSTL, recorrerà els objectes de l’atribut medicaments i els mostrarà.

Estoc de medicaments, capa de servei

El projecte Estoc de medicaments conté la capa de presentació, amb les vistes i els controladors; la capa de domini, amb la representació d’un medicament, i la capa de persistència, que dialoga amb les dades mitjançant objectes repository.

En el projecte actual, els controladors criden directament, via injecció, els objectes repository. No obstant això, si recupereu el diagrama d’arquitectura i procés d’una aplicació Spring MVC, els controladors només dialoguen amb la capa de servei que us falta desenvolupar.

Els objectes repository fan operacions CRUD simples, i els resultats els poden emmagatzemar en memòria en forma d’objectes de domini. Heu vist que obteniu una llista de medicaments perquè està guardada en una List d’objectes Medicament (objectes de domini) al repositori.

Però on posareu operacions que siguin més complexes que abastin més d’una operació CRUD, més d’una entitat, o simplement que afegeixin restriccions? És a dir, on posareu les regles de negoci?

Per exemple, penseu en un moviment d’estoc del nostre medicament que signifiqui baixar 10 unitats d’ibuprofèn. En una primera aproximació podríeu dir que això consisteix a crear un mètode al repositori que faci un update (com és a memòria, seria actualitzar l’element adient a List<Medicament> listOfMedicaments de InMemoryMedicamentRepository).

Però hauríeu de fer més operacions. Hauríeu de veure que el medicament que es vol actualitzar existeix, que té suficient estoc, i després fer efectiva l’actualització. No ho fareu per ara, però a tot això hauríeu d’afegir-li la comprovació de si l’usuari té autorització per fer aquesta operació.

Per contenir aquestes operacions complexes que abasten més d’una operació simple, on podeu fer servir un o més objectes de domini, fareu servir la capa de servei.

Com podeu veure en apartats d’accés a dades, les transaccions es defineixen a la capa de servei.

En el projecte Estoc de medicaments que esteu desenvolupant creareu un servei que faci els moviments d’estoc sobre els medicaments, és a dir, que simplement incrementi o redueixi les unitats en estoc. Llavors, la crida de la capa de presentació es farà com preveu l’arquitectura i el procés d’una aplicació web amb Spring MVC, és a dir, com es mostra en la figura.

Figura Capes i interaccions a Spring MVC

Amb aquest objectiu, creareu un controlador específic que atengui les peticions de moviments d’estoc que cridi el vostre servei i que retorni el resultat. El servei cridarà el repositori i aquest farà l’actualització. Com és a memòria, farà l’actualització sobre la List de medicaments.

Però al repositori només hi ha un mètode que retorna la llista de tots els medicaments. Llavors heu de modificar el repositori per adaptar-lo a les operacions que havíeu enumerat:

  • El medicament que es vol actualitzar existeix.
  • El medicament té suficient estoc (només si és un moviment de decrement).
  • Fer efectiva l’actualització.

Heu d’afegir al repository els mètodes adients per resoldre les operacions.

El codi del projecte “stmedioc” en l’estat de capa de servei es pot descarregar del següent

enllaç ( 10.8 MB )
.

Però per seguir el desenvolupament de la capa de servei és millor partir del que ja heu fet servir per a Estoc de medicaments, capa de persistència, i copiar-lo amb el nom de “stmedioc203” del següent

enllaç ( 10.8 MB )
.

Adaptació del repositori

Heu d’accedir a un medicament concret a partir del seu codi (medicamentId), i això us permetrà comprovar que existeix i veure si es pot fer el moviment d’estoc (ha de tenir prou unitats si voleu treure estoc).

Modifiqueu la interfície MedicamentRepository afegint-hi el mètode Medicament getMedicamentById(String medicamentId);.

Implementeu el mètode a InMemoryMedicamentRepository tal com es mostra a continuació:

    public Medicament getMedicamentById(String medicamentId) {
        Medicament medicamentById = null;
        for (Medicament medicament : listOfMedicaments) {
            if (medicament != null && medicament.getMedicamentId() != null
                    && medicament.getMedicamentId().equals(medicamentId)) {
                medicamentById = medicament;
                break;
            }
        }
        if (medicamentById == null) {
            throw new IllegalArgumentException(
                    "No s'han trobat medicaments amb el codi: " + medicamentId);
        }
        return medicamentById;
    }

El mètode getMetdicamentById cerca el medicament que coincideix en id amb el codi que es passa pel paràmetre. Fixeu-vos que si el troba retorna l’objecte de domini Medicament, i en un altre cas, llança una excepció.

Creació del servei

Heu de crear el servei que faci totes les operacions per aconseguir l’actualització de l’estoc comprovant les regles de negoci: existeix el medicament i no deixeu l’estoc en negatiu.

Com en altres casos fareu servir una interfície, ja que us permet l’acoblament més feble entre les capes.

Creeu un nou paquet cat.xtec.ioc.service i en ell la interfície MovimentStockService amb el codi que es mostra a continuació:

package cat.xtec.ioc.service;

public interface MovimentStockService {
    void processMovimentStock(String medicamentId, long quantity, int signe);
}

Creeu un nou paquet cat.xtec.ioc.service.impl i també la implementació MovimentStockServiceImpl amb el codi que es mostra a continuació:

package cat.xtec.ioc.service.impl;

import cat.xtec.ioc.domain.Medicament;
import cat.xtec.ioc.domain.repository.MedicamentRepository;
import cat.xtec.ioc.service.MovimentStockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MovimentStockServiceImpl implements MovimentStockService {

    @Autowired
    private MedicamentRepository medicamentRepository;

    public void processMovimentStock(String medicamentId, long quantity, int signe) {
        Medicament medicamentById = medicamentRepository.getMedicamentById(medicamentId);
        long signedQuantity = quantity * signe;
        if ((medicamentById.getStockQuantity() + signedQuantity) < 0) {
            throw new IllegalArgumentException("No hi ha prou unitats. La quantitat en estoc és: " + medicamentById.getStockQuantity());
        }
        medicamentById.setStockQuantity(medicamentById.getStockQuantity() + signedQuantity);
    }
}

L’anotació @Autowired fa que Spring us retorni un objecte sense necessitat de crear-lo directament al nostre codi. En aquest cas voleu fer servir MedicamentRepository.

El mètode processMovimentStock fa el conjunt d’operacions que requeríem. Demana al repositori el medicament i fa l’actualització del seu estoc amb setStockQuantity. Però en cap cas deixarà l’estoc en negatiu, perquè abans llançaria l’excepció IllegalArgumentException. Recordeu que al mètode getMedicamentById també es llança una excepció en cas de no existir el medicament.

També us interessa que Spring cerqui automàticament les classes d’aquest paquet per poder injectar els objectes quan es trobi una anotació d’aquest tipus. Per això, canvieu la propietat context:component-scan de DispatcherServlet-servlet.xml:

<context:component-scan base-package="cat.xtec.ioc.controller cat.xtec.ioc.domain.repository cat.xtec.ioc.service" />

Afegits a la capa de presentació

En comptes de fer servir MedicamentController emprareu un nou controlador específic per a aquest cas.

Al paquet cat.xtec.ioc.controller, creeu el controlador MovimentStockController amb el codi següent:

package cat.xtec.ioc.controller;

import cat.xtec.ioc.service.MovimentStockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MovimentStockController {

    @Autowired
    private MovimentStockService movimentStockService;

    @RequestMapping("/movimentestoc/M020/2/-1")
    public String process() {
        movimentStockService.processMovimentStock("M020", 2, -1);
        return "redirect:/medicaments";
    }
}

Amb l’URL de @RequestMapping, el controlador crida la capa de servei per fer el moviment d’estoc. Si hi ha cap excepció es mostrarà el missatge que toca, però si no mostrarà la llista de medicaments on podeu veure el decrement de l’estoc (signe -1 en l’exemple).

En aquest cas, l’URL és fix i es crida a fer el moviment amb constants. No us preocupeu, ja veureu com fer tot això variable.

Fins ara havíeu fet servir la creació explícita del ModelAndView com a resposta, però també es pot retornar un string amb el nom de la vista i Spring crearà l’objecte ModelAndView per a vosaltres.

En el cas que us ocupa heu d’anar a la vista Medicaments, i per evitar tornar a fer el moviment d’estoc, si l’usuari prem el botó Anar enrere del navegador, feu una redirecció (return “redirect:/medicaments”). I per fer servir redirect heu emprat el tipus string com a retorn.

Executeu l’aplicació i aneu a /medicaments. El navegador us mostra la llista de medicaments amb l’estoc actual (vegeu la figura).

Figura Sortida de l’aplicació Estoc de medicaments, estoc actual

Ara canvieu l’URL a /movimentestoc/M020/2/-1 i veureu el següent resultat, que mostra com ha variat l’estoc del paracetamol (vegeu la figura).

Figura Sortida de l’aplicació Estoc de medicaments, estoc modificat

Què heu fet i què heu après amb la capa de servei?

A l’aplicació Estoc de medicaments heu implementat un moviment d’estoc d’un medicament, comprovant que existeix i que no deixeu l’estoc en negatiu.

Per fer això heu seguit les recomanacions d’Spring MVC i heu creat la capa de servei, on s’implementen regles de negoci, és a dir, operacions complexes que poden abastar una o més entitats i que poden contenir operacions simples.

Heu creat un nou controlador que crida la capa de servei, i és aquesta capa la que crida la de persistència. A la vegada, heu afegit els mètodes necessaris a la persistència per complir amb les noves peticions. En el vostre cas, la persistència us proporciona un medicament mitjançant el codi.

Què s'ha après?

A Estoc de medicaments partíeu d’una pàgina de benvinguda, i ara podeu mostrar la llista de medicaments i podeu fer moviments d’estoc (encara de manera fixa).

Per fer això heu estructurat el projecte en capes, tal com es mostra en la figura.

Figura Estructura de capes d’una aplicació Spring MVC

Heu seguit l’estructuració de codi recomanada per a aplicacions web amb Spring MVC amb les capes següents:

  • presentació (Presentation)
  • domini (Domain)
  • servei (Service)
  • persistència (Persistence)

Heu vist que la capa de presentació la conformen les vistes jsp i les classes controladores. A més, mitjançant fitxers de configuració i anotacions, Spring MVC afegeix altres classes necessàries, com el Dispatcher Servlet i View Resolvers.

Heu desenvolupat la persistència amb objectes repositori que dialoguen amb les dades. En el vostre cas no accediu a una base de dades, perquè no és dins de l’abast d’aquesta unitat; treballeu amb dades creades en memòria.

Heu creat la capa domini com la respresentació del nostre model de dades, una classe per a cada entitat. En el vostre cas, una única classe Medicament.

Heu acabat amb la capa de servei, que implementa les regles de negoci. En el vostre cas, un moviment d’estoc que es compon de diverses operacions: comprovació de l’existència del medicament, verificar que l’estoc no queda en negatiu i finalment, actualitzar les unitats en estoc.

Heu connectat tot segons l’arquitectura que proposa Spring MVC (vegeu la figura).

Figura Capes i interaccions a Spring MVC

Heu fet injecció de dependències i l’heu fet mitjançant interfícies per aconseguir un acoblament feble entre les capes que permet un millor manteniment i adaptació futurs.

L’aplicació Estoc de medicaments us mostra una llista de medicaments, i podeu fer un moviment d’estoc. Però us animo a continuar amb aquests materials per descobrir com fer que el moviment d’estoc sigui variable i moltes coses més, com crear productes amb formularis des del navegador.

Anar a la pàgina anterior:
Annexos
Anar a la pàgina següent:
Activitats