Skip to content

Latest commit

 

History

History
305 lines (234 loc) · 9.28 KB

06-1-B-Spring-Data.adoc

File metadata and controls

305 lines (234 loc) · 9.28 KB

Creating RESTful Web Services in Spring

Previously, we used CRUD repository objects for each data type of the domain model and implemented use-cases in service classes. In this section, we will Implement REST API for eventregistration (people, events and registrations) and expose Sping Data.

If you would like to, you can obtain a version of the project that already has the changes from the previous tutorials here.

Preliminaries

  1. Set database URL to start application and accessing the database remotey. You can do the same by either adding an environment variable called SPRING_DATASOURCE_URL or by specifying spring.datasource.url, spring.datasource.username, and spring.datasource.password in src/main/resources/application.properties.

  2. Add the dependency 'spring-boot-starter-data-rest' in build.gradle file of your backend. It is required to expose Spring Data repositories over REST using Spring Data REST.

    implementation 'org.springframework.boot:spring-boot-starter-data-rest'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    
    runtimeOnly 'org.postgresql:postgresql'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

Building a RESTful Web Service

We will discuss two ways to build RESTful web service for our Spring Boot project.

Building a RESTful Web Service Using Controller and DTOs
  1. We will first create a new package in EventRegistration-Backend and then create EventRegistrationRestController class inside it. We have added the annotation @RestController above the controller class so that HTTP requests are handled by EventRegistrationRestController. In addition, we enabled the Cross-Origin Resource Sharing at the controller level using '@CrossOrigin' notation.

    package ca.mcgill.ecse321.eventregistration.controller;
    
    @CrossOrigin(origins = "*")
    @RestController
    public class EventRegistrationRestController {
    }
  2. We further create another package ca.mcgill.ecse321.eventregistration.dto and create the below files inside that package. First we create EventDto.java.

    package ca.mcgill.ecse321.eventregistration.dto;
    
    import java.sql.Date;
    import java.sql.Time;
    
    public class EventDto {
    
    	private String name;
    	private Date eventDate;
    	private Time startTime;
    	private Time endTime;
    
    	public EventDto() {
    	}
    
    	public EventDto(String name) {
    		this(name, Date.valueOf("1971-01-01"), Time.valueOf("00:00:00"), Time.valueOf("23:59:59"));
    	}
    
    	public EventDto(String name, Date eventDate, Time startTime, Time endTime) {
    		this.name = name;
    		this.eventDate = eventDate;
    		this.startTime = startTime;
    		this.endTime = endTime;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public Date getEventDate() {
    		return eventDate;
    	}
    
    	public Time getStartTime() {
    		return startTime;
    	}
    
    	public Time getEndTime() {
    		return endTime;
    	}
    
    }
  3. Next, we create PersonDto Java class.

    package ca.mcgill.ecse321.eventregistration.dto;
    
    import java.util.Collections;
    import java.util.List;
    
    public class PersonDto {
    
    	private String name;
    	private List<EventDto> events;
    
    	public PersonDto() {
    	}
    
    	@SuppressWarnings("unchecked")
    	public PersonDto(String name) {
    		this(name, Collections.EMPTY_LIST);
    	}
    
    	public PersonDto(String name, List<EventDto> arrayList) {
    		this.name = name;
    		this.events = arrayList;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public List<EventDto> getEvents() {
    		return events;
    	}
    
    	public void setEvents(List<EventDto> events) {
    		this.events = events;
    	}
    
    }
  4. Finally, we create RegistrationDto Java class.

    package ca.mcgill.ecse321.eventregistration.dto;
    
    public class RegistrationDto {
    
    	private PersonDto person;
    	private EventDto event;
    
    	public RegistrationDto() {
    	}
    
    	public RegistrationDto(PersonDto person, EventDto event) {
    		this.person = person;
    		this.event = event;
    	}
    
    	public PersonDto getperson() {
    		return person;
    	}
    
    	public void setperson(PersonDto person) {
    		this.person = person;
    	}
    
    	public EventDto getEvent() {
    		return event;
    	}
    
    	public void setEvent(EventDto event) {
    		this.event = event;
    	}
    }
  5. Now, we will add the methods in the controller class. Also, we will add annotaions to map web requests.

    	@PostMapping(value = { "/persons/{name}", "/persons/{name}/" })
    	public PersonDto createPerson(@PathVariable("name") String name) throws IllegalArgumentException {
    		// @formatter:on
    		Person person = service.createPerson(name);
    		return convertToDto(person);
    	}

@RequestMapping annotation is used to map web requests to Spring Controller methods. Since, @RequestMapping maps all HTTP operations by default. We can use @GetMapping, @PostMapping and so forth to narrow this mapping.

Moreover, in the above snippet, we use "value" and @PathVariable to bind the value of the query string parameter name into the name parameter of the createPerson() method.

  1. You can add other methods similarly with appropriate mappings.

    @PostMapping(value = { "/events/{name}", "/events/{name}/" })
    public EventDto createEvent(@PathVariable("name") String name, @RequestParam Date date,
    @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.TIME, pattern = "HH:mm") LocalTime startTime,
    @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.TIME, pattern = "HH:mm") LocalTime endTime)
    throws IllegalArgumentException {
    	Event event = service.createEvent(name, date, Time.valueOf(startTime), Time.valueOf(endTime));
    	return convertToDto(event);
    }
    
    @GetMapping(value = { "/events", "/events/" })
    public List<EventDto> getAllEvents() {
    	List<EventDto> eventDtos = new ArrayList<>();
    	for (Event event : service.getAllEvents()) {
    		eventDtos.add(convertToDto(event));
    	}
    	return eventDtos;
    }
    
    @PostMapping(value = { "/register", "/register/" })
    public RegistrationDto registerPersonForEvent(@RequestParam(name = "person") PersonDto pDto,
    	@RequestParam(name = "event") EventDto eDto) throws IllegalArgumentException {
    	Person p = service.getPerson(pDto.getName());
    	Event e = service.getEvent(eDto.getName());
    
    	Registration r = service.register(p, e);
    	return convertToDto(r, p, e);
    }
    
    @GetMapping(value = { "/registrations/person/{name}", "/registrations/person/{name}/" })
    public List<EventDto> getEventsOfPerson(@PathVariable("name") PersonDto pDto) {
    	Person p = convertToDomainObject(pDto);
    	return createEventDtosForPerson(p);
    }
    
    @GetMapping(value = { "/events/{name}", "/events/{name}/" })
    public EventDto getEventByName(@PathVariable("name") String name) throws IllegalArgumentException {
    	return convertToDto(service.getEvent(name));
    }
    
    private EventDto convertToDto(Event e) {
    	if (e == null) {
    		throw new IllegalArgumentException("There is no such Event!");
    	}
    	EventDto eventDto = new EventDto(e.getName(),e.getDate(),e.getStartTime(),e.getEndTime());
    	return eventDto;
    }
    
    private PersonDto convertToDto(Person p) {
    	if (p == null) {
    		throw new IllegalArgumentException("There is no such Person!");
    	}
    	PersonDto personDto = new PersonDto(p.getName());
    	personDto.setEvents(createEventDtosForPerson(p));
    	return personDto;
    }
    
    private RegistrationDto convertToDto(Registration r, Person p, Event e) {
    	EventDto eDto = convertToDto(e);
    	PersonDto pDto = convertToDto(p);
    	return new RegistrationDto(pDto, eDto);
    }
    
    private Person convertToDomainObject(PersonDto pDto) {
    	List<Person> allPersons = service.getAllPersons();
    	for (Person person : allPersons) {
    		if (person.getName().equals(pDto.getName())) {
    			return person;
    		}
    	}
    	return null;
    }
    
    private List<EventDto> createEventDtosForPerson(Person p) {
    	List<Event> eventsForPerson = service.getEventsAttendedByPerson(p);
    	List<EventDto> events = new ArrayList<>();
    	for (Event event : eventsForPerson) {
    		events.add(convertToDto(event));
    	}
    	return events;
    }

Test the Service

We can test the application using, e.g., the RESTClient browser plugin, Advanced Rest Client, Postman or curl.

Once you launch the client, you can specify the path and select the method as shown in the below figures.

Post method on REST Client

Once we use POST, the record is persisted and then we can use the GET method to retrive the same.

GET method on REST Client

Similary, we can test the other methods.

Spring Data - an Alternative to Exposing the Database

The advantage of using Spring Data Rest is that it can remove a lot of boilerplate that’s natural to REST services. Spring would automatically create endpoints like /events, /people as we saw above and these endpoints can be further customized.

  1. We have already added the dependency 'spring-boot-starter-data-rest' in preliminary section to to expose Spring Data repositories over REST using Spring Data REST.

  2. Next, we can go to repository interfaces and add @RepositoryRestResource annotaion.

    @RepositoryRestResource(collectionResourceRel = "participants", path = "participants")
    public interface PersonRepository extends CrudRepository<Person, String>{
    
    	Person findPersonByName(String name);
    
    }
  3. Finally, we can access this REST API in the browser or REST Client and will receive the JSON as shown below.

Post method on REST Client