Web Service using Mongo DB

Under construction

Preparing Environment

Launch Mongo and Config Server:

$ docker run --name mongo -d -p 27017:27017 mongo
$ docker run -d --name=config-service -p 8888:8888 msvcdojo/config-service:0.0.1

Building Profiles Service

Create a basic web Service with the name profiles-service.

In the build.gradle add additional dependency:

profiles-service/build.gradle
compile("org.springframework.boot:spring-boot-starter-data-rest:$springBootVersion")
compile("org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion")
compile("org.springframework.hateoas:spring-hateoas")
Under construction

Add a Profile entity describing a document that will be stored in the Database:

src/main/java/msvcdojo/ProfilesServiceApplication.java
@Document(collection="profiles")
class Profile {
    public Profile() {
    }

    @Id
    private String id;
    @Indexed
    private String fullName;
    private List<String> photos;

    public void setId(String id) {
        this.id = id;
    }
    public void setKey(String key) {
        this.id = key;
    }
    public void setFullName(String fullName) {
        this.fullName = fullName;
    }
    public void addPhotoReference(String photoId) {
        this.photosList().add(photoId);
    }

    public String getKey() {
        return id;
    }
    public String getFullName() {
        return fullName;
    }
    public Integer getPhotoCount() { return this.photosList().size(); }
    public List<String> photosList() {
        if (this.photos == null)
            this.photos = new ArrayList<>();
        return this.photos;
    }

    @Override
    public String toString() {
        return String.format(
                "Customer[id=%s, fullName='%s']",
                id, fullName);
    }
}

Add a PhotoResource. We’re going to store binary data in the "files" section of MongoDB.

src/main/java/msvcdojo/ProfilesServiceApplication.java
class PhotoResource extends AbstractResource {

    private final Photo photo;

    public PhotoResource(Photo photo) {
        Assert.notNull(photo, "Photo must not be null");
        this.photo = photo;
    }

    @Override
    public String getDescription() {
        return null;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return this.photo.getInputStream();
    }

    @Override
    public long contentLength() throws IOException {
        return -1;
    }

}

interface Photo {

    /**
     * @return a new {@link InputStream} containing photo data as a JPEG. The caller is
     * responsible for closing the stream.
     * @throws IOException
     */
    public InputStream getInputStream() throws IOException;

}

Now it’s time to add a data repository description with it’s "verbs":

src/main/java/msvcdojo/ProfilesServiceApplication.java
@RepositoryRestResource
interface ProfilesRepository extends MongoRepository<Profile, String> {

    @Query("{ '_id' : ?0 }")
    Profile findByKey(@Param("key") String key);

    Profile findByFullName(@Param("address") String address);

}

The repository is annotated with @RepositoryRestResource making it a REST controller out of the box. This exposes, in part, GET method to retrieve a list of stored items and a POST method to insert new items.

We’re going to add a REST controller that will allow us to insert a photo (large binary file) with any arbitrary key by posting data into the URL that looks like this: http://host/profiles/{key}/photos

Create a rest controller with the mapping /profiles/{key}/photos:

src/main/java/msvcdojo/ProfilesServiceApplication.java
@RestController
@RequestMapping(value = "/profiles/{key}/photos", produces = MediaType.APPLICATION_JSON_VALUE)
class ProfilePhotoController {

Add a handler that will process POST requests:

src/main/java/msvcdojo/ProfilesServiceApplication.java
@RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT})
ResponseEntity<Resource<Profile>> insertPhoto(@PathVariable String key,
                                                   @RequestParam MultipartFile file) throws IOException {
    Photo photo = file::getInputStream;
    Profile profile = this.profilesRepository.findOne(key);
    String id = key + profile.getPhotoCount();
    try (InputStream inputStream = photo.getInputStream()) {
        this.fs.store(inputStream, id);
    }
    profile.addPhotoReference(id);
    this.profilesRepository.save(profile);
    URI uri = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/{id}/photo").buildAndExpand(id).toUri();
    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(uri);
    return new ResponseEntity<>(
            this.readPhoto(key), headers, HttpStatus.CREATED);
}

Follow the solution further …​

For demo purposes only our service is going to clean up data on start.

The cleanup is executed by CommandLineRunner lambda:

src/main/java/msvcdojo/ProfilesServiceApplication.java
@Bean
CommandLineRunner init(ProfilesRepository profilesRepository) {
    return a -> profilesRepository.deleteAll();
}

Play time

Let’s add some data. One profile and one photo for that profile:

$ gradlew clean prepDocker
$ java -jar -Dconfig-service.uri=http://dockerhost:8888 -Dmongoserver=dockerhost build\libs\profiles-service-0.0.1.jar
$ echo { "key":"john", "fullName":"John Smith" } | curl -H "Content-Type: application/json" -d @- http://localhost:8101/profiles
{{
  "fullName" : "John Smith",
  "key" : "john",
  "photoCount" : 0,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8101/profiles/john"
    },
    "profile" : {
      "href" : "http://localhost:8101/profiles/john"
    },
    "photos" : {
      "href" : "http://localhost:8101/profiles/john/photos"
    }
  }
}
$ curl -F "file=@../../../misc/face.png" http://localhost:8101/profiles/john/photos
{
  "fullName" : "John Smith",
  "key" : "john",
  "photoCount" : 1
}

Let’s check what photos are stored for a profile:

$ curl http://localhost:8101/profiles/john/photos
[ {
  "content" : "john0",
  "links" : [ {
    "rel" : "john0",
    "href" : "http://localhost:8101/profiles/john/photos/john0/photo"
  } ]
} ]

Navigate in your browser of choice to the URL provided by the last response and you should see the uploaded picture.

Bonus

  • Explore the contents of MongoDB by launching Mongo Express web client in docker and navigating to http://dockerhost:8081:

$ docker run -d -p 8081:8081 --link mongo:mongo knickers/mongo-express