• Sat. Oct 26th, 2024

Hands-on with Dropwizard REST APIs

Byadmin

Dec 23, 2021


Dropwizard is a REST-oriented framework that draws together several Java packages into a cohesive whole. It is an alternative to Spring (and Spring’s WebMVC package). Dropwizard delivers a more streamlined experience. It adopts even more configuration by convention than Spring, and eliminates most of the API surface that is unrelated specifically to delivering REST APIs.Start a new Dropwizard projectLet’s begin by scaffolding a new project via the official Dropwizard Maven archetype. Find a comfortable place on your local system, and from the command line, enter the command in Listing 1.Listing 1. Run the archetypemvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=2.0.0This will run in interactive mode. I used a group ID of com.infoworld and an artifact ID of Demo. I also used Demo for the name.Once the archetype finishes deploying itself, you can cd into your directory (in my case, cd Demo). Now you install the dependencies with mvn clean package.Now the app can be run withjava -jar target/Demo-1.0-SNAPSHOT.jar server (Remember to use the app name you gave it if different from Demo.) If you visit localhost:8080 now, you’ll be greeted with a default JSON-formatted “not found” error:{“code”:404,”message”:”HTTP 404 Not Found”}Map an endpointSo far, the app only returns 404s because no endpoints are mapped. I want to give you a clear understanding of how this is accomplished with Dropwizard. We’ll isolate the process by mapping a simple string endpoint. Step 1 is to create a class that acts as the path handler. Remember, Dropwizard’s calling in life is to unite several best-in-class libraries into an easy-to-use package. For supporting RESTful endpoints, Dropwizard uses Jersey. Your app already has Jersey fully set up. The path mappings are accomplished with the Jersey syntax.Step 2 is to register the handler class with the app. Let’s take these two steps in turn.Create a path handler classBy convention, Dropwizard endpoint handlers go in the /src/main/java/com/infoworld/resources folder (or whatever group ID you choose). We’re going to add an endpoint handler for returning a list of songwriters. As you can see in Listing 2, we’ll return only the best. Create a new SongWriters.java file in the /resources directory.Listing 2. The SongWriters endpointpackage com.infoworld.resources;import javax.ws.rs.*;import javax.ws.rs.core.MediaType;import java.util.List;import java.util.Optional;@Path(“/songwriters”)@Produces(MediaType.APPLICATION_JSON)public class SongWriters {    public SongWriters() {    }    @GET    public String getBrands() {        return “{‘name’:’Roger Waters’,’name’:’Tom Petty’}”;    }}Listing 2 is fairly straightforward. The class itself is given a path via the @Path annotation, and JSON is specified as the media type via the @Produces annotation. Next, the getBrands() method, which returns a string, is mapped to the GET HTTP method via the @GET annotation. The method just returns a hard-coded string representing the set of songwriters. Register the handlerNow to register the handler. Open the /src/main/java/com/infoworld/DemoApplication.java file and modify the run() method to look like Listing 3.Listing 3. Registering the SongWriters handlerimport com.infoworld.resources.SongWriters;// …public void run(final DemoConfiguration configuration, final Environment environment) {        SongWriters songWriters = new SongWriters();        environment.jersey().register(songWriters);    }Again, fairly straightforward. Listing 3 imports the class you just created, then uses the Environment object to access Jersey and register the handler. Notice the run method also receives a DemoConfiguration object, which you’ll see in action momentarily.Test the handlerNow if you visit localhost:8080/songwriters, you’ll get the JSON that you hard coded. Notice the system has seamlessly handled returning the string as JSON.Model the domainNow you want to move from a hard-coded string to using actual domain objects. Start by creating a /src/main/java/com/infoworld/domain/SongWriter.java file, and give it the contents of Listing 4. Listing 4. The SongWriter domain model classpackage com.infoworld.domain;import java.util.ArrayList;import java.util.List;public class SongWriter {    private final String name;    private List songs = new ArrayList<>();    public SongWriter(final String name, final List songs) {        this.name = name;        this.songs = songs;    }    public String getName() {      return name;    }    public List getSongs(){      return this.songs;    }}Add a repository classNow we’ll add a repository class to represent the data access layer. Create a new folder and file: /src/main/java/com/infoworld/repo/SongWriterRepo.java.Listing 5. The SongWriterRepo classpackage com.infoworld.repo;import com.infoworld.domain.SongWriter;import java.util.List;import java.util.ArrayList;import java.util.Optional;import java.util.stream.Collectors;public class SongWriterRepo {  private final List songWriters;  public SongWriterRepo(){    this.songWriters = new ArrayList();       }  public SongWriterRepo(List songWriters){    this.songWriters = songWriters;  }  public List findAll(){    return songWriters;  }  public Optional getByName(final String name){    return songWriters.stream().filter(sw -> sw.getName().equals(name)).findFirst();  }}As you can see in Listing 5, SongWriterRepo is responsible for returning all the SongWriter objects in a List via the findAll() method, while the getByName() method grabs the songwriter specified by the passed in string name.Use the domain layerNow we’ll use the new layer to drive the JSON returned by the resource endpoint. Modify com.infoworld.resource.SongWriters to look like Listing 6.Listing 6. The modified SongWriters classpackage com.infoworld.resources;import javax.ws.rs.*;import javax.ws.rs.core.MediaType;import java.util.List;import java.util.Optional;import com.infoworld.repo.SongWriterRepo;import com.infoworld.domain.SongWriter;@Path(“/songwriters”)@Produces(MediaType.APPLICATION_JSON)public class SongWriters {   private final SongWriterRepo repo;    public SongWriters(SongWriterRepo repo) {      this.repo = repo;    }    @GET    public List getBrands() {        return repo.findAll();    }}Listing 6 has eliminated the hard-coded string in favor of referencing the SongWriterRepo class, which is set in the constructor. Now take a look at Listing 7, where these classes are wired together in the DemoApplication class.Listing 7. Wiring the domain class in DemoApplicationpackage com.infoworld;import io.dropwizard.Application;import io.dropwizard.setup.Bootstrap;import io.dropwizard.setup.Environment;import com.infoworld.resources.SongWriters;import com.infoworld.repo.SongWriterRepo;public class DemoApplication extends Application {// …    @Override    public void run(final DemoConfiguration configuration, final Environment environment) {        SongWriterRepo swRepo = new SongWriterRepo();        SongWriters songWriters = new SongWriters(swRepo);        environment.jersey().register(songWriters);    }}Return to the DemoApplication class, and modify it as shown in Listing 7. Notice that you now instantiate the SongWriterRepo object and parameterize the SongWriters resource object before handing it off to Jersey.Now if you rebuild the project and visit localhost:8080/songwriters, you will find an empty array. Let’s add a hard-coded songwriter as shown in Listing 8.Listing 8. Adding a songwriterpublic void run(final DW2Configuration configuration,                    final Environment environment) {        List preload = new ArrayList();        preload.add(new SongWriter(“Mark Knopfler”, new ArrayList()));        SongWriterRepo swRepo = new SongWriterRepo(preload);        SongWriters songWriters = new SongWriters(swRepo);        environment.jersey().register(songWriters);    }Now you’ll get a result when you hit the endpoint.Use the config fileAdd a new config file at src/main/resources/Demo-Config.yml and put the contents of Listing 9 in it.Listing 9. The config YAML—songWriters:  – John Lennon  – Jeff LynneNow change the com.infoworld.DemoConfiguration.java file to look like Listing 10. This file is used to model the config file via Jackson.Listing 10. Config javapackage com.infoworld;import io.dropwizard.Configuration;import com.fasterxml.jackson.annotation.JsonProperty;import org.hibernate.validator.constraints.*;import javax.validation.constraints.*;import com.fasterxml.jackson.annotation.JsonCreator;import com.fasterxml.jackson.annotation.JsonProperty;import java.util.List;import java.util.ArrayList;public class DW2Configuration extends Configuration {  private List<String> songWriters;  @JsonCreator  public DW2Configuration(@JsonProperty(“songWriters”) List<String> songWriters){          this.songWriters = songWriters;  }  public List<String> getSongWriters(){          return this.songWriters;  }}Now use the file in DemoApplication, as demonstrated in Listing 11.Listing 11. Using the config file to preload songwriters in DemoApplication.java    @Override    public void run(final DemoConfiguration configuration,                    final Environment environment) {        List preload = new ArrayList();        configuration.getSongWriters().forEach(sw -> preload.add(new SongWriter(sw, new ArrayList())));        SongWriterRepo swRepo = new SongWriterRepo(preload);        SongWriters songWriters = new SongWriters(swRepo);        environment.jersey().register(songWriters);    }The main point of the change in Listing 11 is that the configuration file is hydrated into the DemoConfiguration class, populating the getSongWriters() method that is used to preload the repository class.Run the app with the config fileA small change is required to run this app with the config file. Notice in Listing 12 that the file is referenced after the call to server.Listing 12. Running the app with the config filejava -jar target/DW2-1.0-SNAPSHOT.jar server src/main/resources/DW2-config.ymlNow when you visit the app at localhost:8080/songwriters, you’ll see the preloaded songwriters.Dropwizard is a lean alternative to Spring-based RESTful APIs. As you’ve seen here, it focuses on providing a simple but sufficient stack to address these demands.The sources for the demo app are available here.

Copyright © 2021 IDG Communications, Inc.



Source link