Sample Application

Discover how to program interactions with the Couchbase Server via the data, Query, and search services — using the Travel Sample Application with the built-in Travel Sample data Bucket. This version also demonstrated the Developer Preview of the upcoming Collections feature.

Travel Sample Application uses the Travel Sample data Bucket, which ships with Couchbase Server. For Couchbase Server 6.5, make sure that you have at least one node each of data; query; index; and search. For a development box, mixing more than one of these on a single node (given enough memory resources) is perfectly acceptable. If you have yet to install Couchbase Server in your development environment, start here.

Then load up the Travel Sample Bucket, using either the Web interface or the command line. You will also need to create a Search Index — Query indexes are taken care of by the Sample Bucket.

This version of the Travel Sample App uses the Collections Developer Preview (DP) — enabling Developer Preview should only be done on a development machine; there is no upgrade path available from a DP-enabled Couchbase Server.

Preparation

As well as the Java SDK 3.0 and Couchbase Server, set up as described above, you will need git to fetch the travel sample application code:

git clone git@github.com:couchbaselabs/try-cb-java.git

Change directory into your cloned repository, and check out the latest branch (this will most probably be enabled as the default branch).

git checkout 6.5.0-branch
  • Before building the Travel Sample Application, which in the case of the Java SDK utilises the Collections Developer Preview, you need to enable this DP feature (see warning above).

    Enable Collections Developer Preview
    /opt/couchbase/bin/couchbase-cli enable-developer-preview --enable -c http://localhost:8091 -u Administrator -p password
    Developer preview cannot be disabled once it is enabled. If you enter developer preview mode you will not be able to upgrade. DO NOT USE IN PRODUCTION.
    Are you sure [y/n]: y

The Travel Sample Bucket needs altering to be split into collections. There is a script to do this included with the Travel Sample App — run:

Create Sample Collections
sh create-collections.sh Administrator password 127.0.0.1

adjusting for any changes you have made to server URL, or admin password. You should now have the Travel Sample Data Bucket split into collections:

{"uid":"1"}{"uid":"2"}{"uid":"3"}

THE FINAL RESULT
{"uid":"3","scopes":[{"name":"userData","uid":"8","collections":[{"name":"flights","uid":"9"},{"name":"users","uid":"8"}]},{"name":"_default","uid":"0","collections":[{"name":"_default","uid":"0"}]}]}

Running the Travel Sample Application

Next, edit the storage.host field in src/main/resources/application.properties to the one for your containerised Couchbase Server (or localhost, 127.0.0.1, if appropriate), and any other local changes — such as password. From here onwards, we’ll assume the defaults.

And run with

mvn spring-boot:run

Most likely, you’ll want to open up your preferred IDE for the storage.host step, and stay there to build the app, rather than running Maven from the command line.

After the build, you should see messages from Tomcat and trycb.Application, which tells you that you’ve been successful. With your Web browser of choice, head to port 8080 of the local machine.

Using the Sample App

Give yourself a username and password and click Register.

Now try out a few queries, and see Search in action for the hotel finder feature..

Sample App Backend

The backend code shows Couchbase Java SDK in action with Query and Search, but also how to plug together all of the elements and build an application with Couchbase Server and the Java SDK. Look at User.java to see some of the pieces necessary in most applications, such as the User @Service:

@Service
public class User {

    private final TokenService jwtService;

    @Autowired
    public User(TokenService jwtService) {
        this.jwtService = jwtService;
    }

    static final String USERS_COLLECTION_NAME = "users";
    static final String FLIGHTS_COLLECTION_NAME = "flights";

Here showing users and flights as Collections. Creating a user shows the typical security concerns, with salted password hashes, as well as the mundane but essential business of using the KV interface to insert the username into the database:

    public Result<Map<String, Object>> createLogin(final Scope scope, final String username, final String password,
            DurabilityLevel expiry) {
        String passHash = BCrypt.hashpw(password, BCrypt.gensalt());
        JsonObject doc = JsonObject.create()
            .put("type", "user")
            .put("name", username)
            .put("password", passHash);
        InsertOptions options = insertOptions();
        if (expiry.ordinal() > 0) {
            options.durability(expiry);
        }
        String narration = "User account created in document " + username + " in bucket " + scope.bucketName()
                + " scope " + scope.name() + " collection " + USERS_COLLECTION_NAME
                + (expiry.ordinal() > 0 ? ", with expiry of " + expiry.ordinal() + "s" : "");

        try {
            scope.collection(USERS_COLLECTION_NAME).insert(username, doc);
            return Result.of(
                    JsonObject.create().put("token", jwtService.buildToken(username)).toMap(),
                    narration);
        } catch (Exception e) {
            e.printStackTrace();
            throw new AuthenticationServiceException("There was an error creating account");
        }
    }

Here, the flights array, containing the flight IDs, is converted to actual objects:

        Collection flightsCollection = scope.collection(FLIGHTS_COLLECTION_NAME);
        List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
        for (int i = 0; i < flights.size(); i++) {
            String flightId = flights.getString(i);
            GetResult res;
            try {
                res = flightsCollection.get(flightId);
            } catch (DocumentNotFoundException ex) {
                throw new RuntimeException("Unable to retrieve flight id " + flightId);
            }
            Map<String, Object> flight = res.contentAsObject().toMap();
            results.add(flight);
        }
        return results;
    }

}