Sunday, January 11, 2015

Construindo um CRUD com Spring, REST e Mongo DB parte 4

Passo 4 – Repositórios com MongoDB


Neste último passo, a persistência em memória será trocada por uma solução NoSQL. Para que esse passo funcione, deve-se instalar primeiramente o MongoDB. Não foi necessária nenhuma configuração adicional, tais como local de dados, journaling, logs, entre outros. Decida essas configurações do modo que for melhor.

MongoDB é um banco de dados baseado em documentos da geração NoSQL, que significa não estar preso ao modelo relacional. No entanto, existem problemas que nos bancos relacionais não existem, tais como atualizações não garantidas ou a falta de um relacionamento mais forte, por exemplo.

Por outro lado, não se está preso em um modelo pré-definido de uma tabela de dados, com regras restritivas para trabalhar com eles. Cada registro de uma entidade pode ter suas próprias regras existindo ou não alguns campos preenchidos. Não há a necessidade de atribuir a um campo um valor null, porém essas informações devem ser tratadas na aplicação de alguma forma.

Especificamente o banco de dados MongoDB, trabalha com documentos de dados BSON, um formato derivado do JSON. Também chamado de binary JSON, esse formato foi projeto para ser eficiente em armazenamento e leitura de informações.

Para lidar com MongoDB, deve-se adicionar algumas dependências do Spring que lidam com banco de dados e com o banco de dados em questão. Deve-se alterar o código fonte em alguns pontos da aplicação para essa adaptação.

Deve-se, primeiramente, adicionar as dependências do Spring-data e do MongoDB no pom.xml. Note que no passo anterior não foi adicionado o Spring-data, pois os dados foram gravados em memória.

(1)
groupId.....: org.springframework.data
artifactId..: spring-data-mongodb
version.....: 1.7.0.DATAMONGO-1118-SNAPSHOT

(2)
groupId.....: org.mongodb
artifactId..: mongo-java-driver
version.....: 2.12.4

O segundo passo é alterar as configurações da classe AppConfig. Deve-se:

  1. Habilitar os pacotes de repositórios, onde se encontrarão as classes de repositório.
  2. Configurar o MongoDB para ser injetado, com configurações oriundas de um arquivo de propriedades.
  3. Retirar a configuração do DAO persistente em memória e apagar as classes

A listagem a seguir mostra os trechos de código a serem considerados:

@Configuration
@PropertySource("classpath:mongodb.properties")
@EnableMongoRepositories(basePackages = "org.crudpeople.repositories")
@ComponentScan(basePackages={"org.crudpeople.controller", "org.crudpeople.service"})
public class AppConfig extends WebMvcConfigurationSupport {

(…)

@Bean
public Mongo mongo() throws UnknownHostException {
return new
MongoClient(
environment.getProperty("database.host")
);
}

@Bean
public MongoTemplate mongoTemplate() throws UnknownHostException {
return new MongoTemplate(mongo(),
environment.getProperty("database.name"));
}
}

Desse modo, toda vez que o servidor estiver no ar, uma conexão com o banco de dados será realizada.

O próximo passo ainda é alterar a entidade para que ela represente um documento no banco de dados MongoDB. Temos que entender que o MongoDB possui um banco de dados, que abriga coleções, que abriga documentos. Neste projeto temos a estrutura descrita na imagem a seguir.


Na classe de configuração foi descrito em qual banco conectar. Na entidade, deve-se anotar que ela é um documento, com a anotação @Document, e a qual coleção pertence esse documento. A listagem abaixo mostra a entidade Pessoa anotada como documento.

@Document(collection = "pessoas")
public class Pessoa {

(…)

}

O que ainda deve ser modificado nessa classe é o ID, cujo tipo deve ser mudado para String. Será aproveitado o id que o MongoDB gera automaticamente para cada documento BSON em uma forma alfanumérica lembando um UUID. Existem formas de implementar um id numérico e sequencial, mas não estamos lidando com bancos de dados sequenciais, e sim com um NOSQL.

O próximo passo é criar a interface de repositório e criar ela no pacote onde foi configurado. Um repositório é uma abstração que o Spring provê para acesso a dados de uma entidade. Não é necessário implementá-la, somente quando existir a necessidade de fazer alguma operação não previstas pelo interface MongoRepository.

@RepositoryDefinition(
domainClass=org.crudpeople.entities.Pessoa.class,
idClass=java.lang.String.class)
public interface PessoaRepository extends MongoRepository{
}

Pronto. Já temos nosso repositório. Nossa camada de persistência está pronta e configurada.

Algumas correções de código devem ser feitas no próximo passo. As classes estarão com algum problema de tipos ou membros do tipo PessoaDAO. Corrigindo esses probleminhas fará com que a aplicação se torne compilável novamente. Existem dois pontos que devem ser considerados nessa migração.

O primeiro ponto é na classe PessoaService no método find, que é responsável por procurar por parte de um nome. Com o repositório não é tão trivial. Deve-se criar um objeto Query e definir os parâmetros. A listagem abaixo mostra a implementação da solução.


public List find(String nome) {
LOGGER.log(Level.INFO, "find");
Query query = new Query();
query.addCriteria(Criteria.where("nome").regex(nome));
return mongoTemplate.find(query, Pessoa.class);
}

Outra modificação é uma alteração pequena na tela de alteração. Ao inicializar a tela, busca-se um registro de pessoa pelo seu id. O problema é que o id gerado pelo MongoDB é alfanumérico e isso atrapalha um pouco o AngularJS. Para resolver esse problema foi colocado o parâmetro do método findById em aspas simples. A listagem abaixo mostra a solução encontrada.

findById('${param['id']}')

Para verificar os registros cadastrados no console do mongo devemos usar a seguinte lista de comandos:

Inicia o console do mongodb

mongo

Usa o banco de dados PessoasDB

use PessoasDB

Pesquisa por todos os documentos na coleção pessoas

db.pessoas.find()

O resultado é:

{ "_id" : ObjectId("54b2ee7744ae7df913204e75"), "_class" : "org.crudpeople.entities.Pessoa", "nome" : "Homer Simpson", "endereco" : "Rua Evergreen Terrace, 742", "telefone" : "(11)12345-1234" }
{ "_id" : ObjectId("54b2ef1644ae7df913204e76"), "_class" : "org.crudpeople.entities.Pessoa", "nome" : "Sheldon Cooper", "endereco" : "Rua Los Robles, 2311", "telefone" : "(11)12345-1234" }
{ "_id" : ObjectId("54b2ef3a44ae7df913204e77"), "_class" : "org.crudpeople.entities.Pessoa", "nome" : "Sherlock Holmes", "endereco" : "Rua Baker, 221B", "telefone" : "(11)12345-1234" }
{ "_id" : ObjectId("54b2ef8144ae7df913204e78"), "_class" : "org.crudpeople.entities.Pessoa", "nome" : "Frd Flintstones", "endereco" : "Rua Cobblestone, 301", "telefone" : "(11)12345-1234" }
{ "_id" : ObjectId("54b2efcb44ae7df913204e79"), "_class" : "org.crudpeople.entities.Pessoa", "nome" : "Peter Griffin", "endereco" : "Rua Spooner, 31", "telefone" : "(11)12345-1234" }
{ "_id" : ObjectId("54b2f00244ae7df913204e7a"), "_class" : "org.crudpeople.entities.Pessoa", "nome" : "Stan Smith", "endereco" : "Rua Cherry, 1024", "telefone" : "(11)12345-1234" }
{ "_id" : ObjectId("54b2f05f44ae7df913204e7b"), "_class" : "org.crudpeople.entities.Pessoa", "nome" : "Charles Xavier", "endereco" : "Pista Graymalkin, 1407", "telefone" : "(11)12345-1234" }


Note que o id, chamado de _id, é um objeto do MongoDB chamado ObjectId e é uma chave hexadecimal UUID. Esse é o id da entidade Pessoa. Adicionalmente, o framework Spring-data MongoDB grava o nome da classe Java que foi usada.

Para acessar o código do projeto no branch do Passo 4, basta baixar o zip no endereço https://github.com/ortolanph/CRUDPeople/tree/passo04.

Conclusão


O framework spring ajuda muito a tirar a complexidade do código, fazendo o programador a focar no negócio. Não é necessário pensar em servlets, abrir conexões com o banco de dados, pensar em como um grafo de objeto deve ser montado. Somente deve ser pensado uma vez e usar no projeto inteiro. O framework faz o resto.

O framework AngularJS ajuda muito a criar uma página web. Não é necessário mais utilizar snippets java para adicionar uma funcionalidade. Basta utilizar o AngularJS para montar uma tabela, trazer dados de um serviço para a tela e ainda obter atualizações em tempo real. Isso ainda sem pensar em tags JSF.

Criar uma aplicação com esses dois frameworks deixa a vida do desenvolvedor um pouco mais fácil. A parte mais importante ainda é fazer o algoritmo do sistema. Mas esses posts criam uma aplicação exemplo, nada real. Deve-se fazer uma prova de conceito, medindo tempo de desenvolvimento, complexidade de implementação e complexidade de integração com outras aplicações com outras tecnologias.

O importante é dar certo!

Wednesday, January 7, 2015

Construindo um CRUD com Spring, REST e Mongo DB parte 3

Passo 3 – A persistência em memória

O pincipal objetivo desse passo não foi me atentar à persistência, mas sim a criação de serviços para criar um CRUD, a criação da interface com o usuário e ligar os serviços com o AngularJS.

Para armazenar os dados, criei uma interface e uma implementação para o meu DAO em memória para que fosse administrado pelo Spring. Desse modo, na classe AppConfig, somente adicionei um método para criar meu DAO. O que preciso fazer é criar um atributo em alguma classe chamado pessoaDAO do tipo PessoaDAO e anotar com @Autowired. Ipso facto magico! O Spring vai injetar essa dependência para você. Segue o código do Bean do spring na classe AppConfig na listagem abaixo:

@Bean
public PessoaDAO pessoaDAO() {
    return new PessoaDAOImpl();
}

A implementação do meu DAO nada mais é do que a utilização de uma lista parametrizada da classe Pessoa, com métodos de inserção, deleção, alteração e busca, ou seja um CRUD.

Não é o ideal, pois quando o servidor é desligado, todos os dados são descartados. Tudo estará registrado enquanto o servidor estiver no ar.

Após a criação do DAO, criei as telas de inclusão, pesquisa e alteração de pessoas. Poderia ter usado uma tela só para inclusão e alteração, mas queria ter algo mais didático.

A página de pesquisa possui um campo para pesquisa por nome ou parte de um nome. Ao acionar o botão de pesquisa, o script irá verificar se o campo foi preenchido para chamar serviços diferentes. Se o campo não for preenchido, a aplicação irá pesquisar por todos os registros, senão pesquisará aqueles registros que possuem aquela parte do nome.

A figura a seguir exibe a tela de resultados para uma parte do nome.



Os registros são exibidos nas linhas da tabela. Cada linha contém o nome com um link para alteração, o endereço, o telefone e a palavra excluir, que chama o serviço para exclusão de um registro, transmitindo seu id e ao retornar refaz a pesquisa. Caso haja algum problema, uma mensagem de erro aparece e os resultados permanecem inalterados.

A tela de cadastro é simples. Não existe mistério. A única coisa a se observar é no script quando é chamado o método POST do objeto $http do AngularJS. O método POST necessita de um parâmetro adicional, que são os dados do POST. A listagem abaixo mostra a construção dos parâmetros.

$scope.createPessoa = function () {
    console.log('Calling URL: ' + $scope.defaultUrl + 'create');

    var data = {'nome': $scope.nome, 'endereco': $scope.endereco, 'telefone': $scope.telefone};
    console.log('Data: ' + data);

    $http.post($scope.defaultUrl + 'create', data)
        .success(function (data) {
             console.log('[OK]');
             $scope.message = data;
        })
        .error(function (data, status) {
             console.log('[FAIL]');
             $scope.message = {'result' : data + status};
        });
};

Uma variável data é criada a partir das informações do formulário, que serão transmitidas em formato JSON.

Essa não é o único lugar que deve mudar. A interface agora está enviando dados em formato JSON e a aplicação também deve receber informações em formato JSON. Para o método create deve-se anotar conforme a listagem abaixo:

@RequestMapping(value = "/create", 
                method = RequestMethod.POST,
                consumes = {"application/json;charset=UTF-8"})
public @ResponseBody
SimpleResult create(@RequestBody Pessoa pessoa) {
    LOGGER.log(Level.INFO, "Serviço /create");
    service.create(pessoa);

    SimpleResult result = new SimpleResult();
    result.setResult("Pessoa cadastrada com sucesso!");

    return result;
}

Duas coisas são importantes de se ressaltar:
1. consumes = {"application/json;charset=UTF-8"}

Isso indica que agora nosso método aceita informações em formato JSON

2. @RequestBody Pessoa pessoa

Isso indica que o objeto deve ser compatível com os atributos da classe Pessoa. Existindo essa combinação, não é necessário fazer mais nada.

A imagem a seguir mostra a tela de cadastro:



Por fim, a tela de alteração. Toda vez que o usuário clicar no link onde está o nome, será encaminhado para a tela de alteração levando no parâmetro, o id da Pessoa. Ao iniciar, o id será pesquisado e populará todos os campos. O id é um campo hidden que eu deixa à vista para fins didáticos. No próximo passo vou mostrar que ele pode conter mais do que imaginamos.

Ao pressionar o botão de alteração, o mesmo mecanismo para a inserção de registros será executado, somente mudando o nome do serviço para alteração e não inclusão.

A imagem a seguir mostra a tela de alteração:



Basicamente o fluxo de trabalho desse passo é dado pelo diagrama de sequência abaixo. Ao fazer um submit na página, o controlador Javascript utiliza o recurso $http do AngularJS que chama um controlador em java, que por sua vez chama um serviço Java que, por sua vez chama a camada de persitência. A clássica arquitetura em três camadas.



Para o próximo passo, será utilizado o banco de dados MongoDB em vez de um banco de dados em memória.

Para acessar o código do projeto no branch do Passo 3, basta baixar o zip no endereço https://github.com/ortolanph/CRUDPeople/tree/passo03.

Tuesday, January 6, 2015

Construindo um CRUD com Spring, REST e Mongo DB parte 2

Passo 2 – Integração entre serviços e interface web

O objetivo do segundo passo é de consumir os serviços criados utilizando AngularJS, um framework Javascript que melhora o HTML estático adicionando atributos e ligando o modelo de dados com as atualizações do servidor. Escolhi AngularJS por ser leve, direto e rápido. Não é tão trivial no início, mas depois de um começo, seu uso se torna intuitivo.

Anteriormente, havia criado alguns serviços que foram chamados pela barra de endereços do browser. Tive que encapsular seus retornos para que formassem um objeto JSON. Isso facilitou a minha camada de visão quando foi chamado pelo AngularJS.

As listagens abaixo mostram os retornos dos serviços em formato JSON:

(1)
Serviço:
/crudpeople/testservice/test

Retorno:
{
 result: "testing"
}


(2)
Serviço:
/crudpeople/testservice/echo/{message}

Retorno:
{
 result: "message"
}

(3)
Serviço:
/crudpeople/testservice/api

Retorno:
{
 result: [
   "/crudpeople/testservice/test",
   "/crudpeople/testservice/echo/{message}",
   "/crudpeople/testservice/api"
 ]
}


Esses serviços vão ser manipulados pelo framework AngularJS, utilizando a linguagem Javascript.

Primeiramente, para começar a utilizar o Angular devemos referenciar o script que está publicado na internet. A tag script referencia a URL https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js para o uso do angular. Depois devemos nosso próprio arquivo de controlador para podermos usar nossa aplicação. Novamente utilizo a tag script, agora para referenciar o meu script services.js.

Meu script, possui alguns elementos referentes ao AngularJS. A primeira linha indica que meu script é um módulo do angular com o nome de serviceTestApp. A listagem abaixo da primeira linha ilustra exatamente isso:

var serviceTestApp = angular.module('serviceTestApp', []);

Agora referencio meu módulo na página, utilizando a notação ng-app na tag html da minha página. O trecho abaixo mostra essa transformação.

html ng-app=”serviceTestApp”

O passo seguinte é construir um controlador para minha aplicação. Construo o controlador chamado TesteController a partir de módulo AngularJS serviceTestApp.

serviceTestApp.controller('TesteController', 
       ['$scope','$http', function($scope, $http) {
    // Insira código aqui
}]);


O trecho acima mostra que criei um controlador chamado TesteController. Ele possui duas variáveis do AngularJS que serão injetadas em sua criação: $scope e $http. $scope conversa com a minha página, transmitindo objetos JSON, Strings e variáveis. Por essa variável, existe uma conversa bidirecional com o meu negócio. Qualquer atualização na página detectada, o $scope é atualizado. $http é meu objeto para comunicação com meus serviços. Posso utilizar os métodos GET, POST, JSONP, por exemplo, para consumir os serviços criados.

Voltando à minha pagina, na tag body, defino que tenho um controlador chamado TesteController e que toda a tramitação de dados será controlada a partir desse escopo. Posso ter um controlador que vai servir a um escopo somente em um div ou span, mas isso seria restringir ao máximo o uso do AngularJS. O trecho abaixo mostra a ligação do controlador com a tag body.

body ng-controller=”TesteController”

Desse modo, o AngularJS está pronto para ser utilizado, ou seja, pronto para ligar a interface gráfica aos serviços implementados.

Do lado do cliente, os serviços são chamados utilizando uma rotina bem parecida. A listagem a seguir mostra esse algoritmo:

    $scope.defaultUrl = "http://localhost:8080/crudpeople/testservice/";

    $scope.test = function() {
        console.log('Calling URL: ' + $scope.defaultUrl + 'test');

        $http.get($scope.defaultUrl + 'test')
            .success(function(data, status) {
                console.log('[OK]');
                $scope.testReturn = {'message' : data.result, 'status': status };
            })
            .error(function(data, status) {
                console.log('[FAIL]');
                $scope.testReturn = {'message' : data, 'status': status };
            });
    };

A rotina define uma variável do controller chamada defaultUrl que contém o caminho padrão para todos os serviços. Desse jeito, basta acrescentar qual o serviço que se deseja acessar.

O método get do objeto $http define duas funções de callback: success que é executado quando tudo ocorre bem e error, quando existe algum problema. Existem outros parâmetros para esses métodos, mas decidi usar somente os dois mais significativos.

Do lado do servidor, quase nada mudou. Agora os métodos retornam um objeto que encapsula as mensagens. No entanto, uma coisa se faz necessária antes de subir o servidor: transformar o retorno em JSON. Os objetos devem possuir uma anotação do framework Jackson que é a @JsonInclude(JsonInclude.Include.NON_NULL). Essa anotação permite que o objeto anotado como @ResponseBody no controller seja transformado em JSON quase que magicamente. O parâmetro JsonInclude.Include.NON_NULL informa ao Jackson que os campos nulos deverão ser ignorados.

A seguir são mostradas as telas de teste dos serviços com os respectivos resultados:




Para acessar o código do projeto no branch do Passo 2, basta baixar o zip no endereço https://github.com/ortolanph/CRUDPeople/tree/passo02.

Construindo um CRUD com Spring, REST e Mongo DB parte 1

Apresentação



Construir aplicações web é complicado. Quando se fala em Java, a coisa fica um pouco mais difícil, pois existem várias e várias soluções. Existem frameworks que facilitam a vida do programador, prometendo evitar boilerplate code, o código que lida com tecnologia.

Quando comecei o programar em Java para web tudo era difícil: não existia uma ferramenta para me ajudar com o deploy (logo após a conclusão do meu projeto veio o Ant), programava utilizando servlets (logo após veio o Struts) e não exisitia um jeito de gerenciar minhas dependências (o Maven ainda demorou um pouco para chegar).

Hoje temos um cardápio imenso de frameworks que evitam que o programador coloque a mão em tecnologia e preste atenção mais no negócio. Essa série de blogs tem a intenção de mostrar uma das soluções que usei para implementar um pequeno CRUD bem básico.

No primeiro post (este aqui), vou apresentar como construir as fundações do projeto. Como configurar o Spring MVC, quais as características que usei e subir um servidor Jetty para exemplificar.

No segundo post, vou apresentar como publicar um serviço rest e consumi-lo utilizando AngularJS, um framework MVVM da Google.

No terceiro post, vou apresentar uma implementação do CRUD utilizando dados em memória, ou seja List. Isso ainda é tecnologia, mas o melhor há por vir!

No quarto e último post, vou retirar essa implementação da persistência em memória e substituir por uma solução com Repositories e MongoDB.

Vamos lá para o primeiro post então!


Passo 1 - Criação da Estrutura do Projeto




Para iniciar, usei a IDE Netbeans e criei um projeto WEB, sem servidor pré-definido. Minha intenção é ter um projeto livre de servidor, pronto para eu fazer um deploy em qualquer servidor. Escolhi o servidor Jetty que pode ser inicializado com o Maven.

Depois disso alterei o pom.xml para colocar as dependências do Spring. Segue a tabela com as dependências do Spring.

(1)
groupId.....: javax
artifactId..: javaee-web-api
version.....: 7.0
scope.......: provided


(2)
groupId.....: org.springframework
artifactId..: spring-core
version.....: 4.1.1.RELEASE

(3)
groupId.....: org.springframework
artifactId..: spring-context
version.....: 4.1.1.RELEASE

(4)
groupId.....: org.springframework
artifactId..: spring-context-support
version.....: 4.1.1.RELEASE

(5)
groupId.....: org.springframework
artifactId..: spring-webmvc
version.....: 4.1.1.RELEASE
  
Para que o maven consiga resolver as dependências do spring de forma correta, adicionei a tag repositories para o meu pom.xml, conforme tabela abaixo.

(1)
id..: SpringSource Snapshots
url.: http://repo.springsource.org/libs-snapshot
enabled: true

(2)
id..: SpringSource Milestones
url.: http://repo.springsource.org/libs-milestone

Para iniciar o framework Spring de uma forma programática, criei uma classe chamada AppConfig.java. Essa é uma classe de configuração que elimina a necessidade do arquivo de configuração xml. Ela começa com a annotation @Configuration assinalando que essa classe é de configuração do Spring. Utilizei a annotation @ComponentScan para que o Spring verificasse em alguns pacotes os meus componente de serviço e meus controladores.

Ainda sobre inicialização, criei a classe CRUDPeopleWebInitializer.java que implementa WebApplicationInitializer do framework Spring. Essa classe é responsável pela criação de todos os objetos de minha configuração e inseri-las em um contexto do Spring.

Para publicar meu serviço inicial, criei a classe TestController.java, que publica somente três serviços:

  1. http://localhost:8080/crudpeople/testservice/test
  2. http://localhost:8080/crudpeople/testservice/echo/{message}
  3. http://localhost:8080/crudpeople/testservice/api

Ao acessar o primeiro serviço, o usuário receberá a mensagem testing na tela. O segundo serviço deve ter uma mensagem que será replicada. O terceiro serviço irá mostrar um array das URLs listadas acima.

Anote com @Controller sua classe para que ela vire um componente do Spring. Anote a classe com @RequestMapping para que os métodos passem a ser vistos como um serviço. Adicione um valor padrão que será considerado como um caminho raiz para cada serviço a ser publicado. Indiquei que todos meus serviços produzem uma resposta em JSON. O código abaixo exemplifica o uso dessa anotação:

@RequestMapping(value = "/crudpeople/testservice/*",
        produces = {"application/json;charset=UTF-8"})

Para cada método público que quero publicar como serviço, anoto novamente com @RequestMapping, porém, dessa vez, digo para ele qual o nome do meu serviço a ser publicado. Como exemplo, o meu serviço test:

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public @ResponseBody
    String test() {
        LOGGER.log(Level.INFO, "Callling /test service");
        return "testing";
    }

Preciso indicar qual o nome do meu serviço (value) e qual o método pelo qual ele será chamado (RequestMethod.GET). Note que ainda existe uma outra anotação @ResponseBody antes do retorno na assinatura do método, o que indica que quando meu método retornar alguma coisa ele retornará o objeto que o método retorna. Poderia criar um objeto mais complexo, ao invés de retornar uma String, mas meu objetivo agora é somente testar a publicação de serviço.

Para acessar, devemos iniciar o servidor Jetty utilizando o maven pela linha de comando. Se gosta de utilizar sua IDE para executar o maven, não existe problemas. Iniciando pela linha de comando deve-se digitar, no diretório raiz do projeto, onde se encontra o arquivo pom.xml:

mvn dependency:resolve clean install jetty:run

O que faz o Jetty ser executado é um plugin na tag build do arquivo pom.xml. Seguem as informações do plugin:

groupId.....: org.mortbay.jetty
artifactId..: jetty-maven-plugin
version.....: ${jetty.maven.plugin}

Defini uma propriedade para a variável ${jetty.maven.plugin} = 8.1.5.v20120716.

Se tudo der certo (e qual a razão de não dar certo?) é possível acessar essas URLs em seu browser. Use o Chrome para uma melhor renderização. As figuras abaixo mostram o resultado desse primeiro passo.






Para acessar o código do projeto no branch do Passo 1, basta baixar o zip no endereço https://github.com/ortolanph/CRUDPeople/tree/passo01.

Próximo passo, usar AngularJS para consumir os serviços e exibir os resultados no browser.