This is a follow up to my previous post on getting setup with Grails. In this post we are going to write a url shortening service in grails based on requirements from educative (Credits to them for giving all the details). On the way, we will discuss the different ways of achieving things in grails and solve the problem . So the problem statement is as follows

  1. Given a URL, our service should generate a shorter and unique alias of it.
  2. When users access a short link, our service should redirect them to the original link
  3. Users should optionally be able to pick a custom short link for their URL.
  4. Links will expire after a standard default time span. Users should be able to specify the expiration time.

The above requirement is implemented in the below github repository

soundarmoorthy/atlantis
Contribute to soundarmoorthy/atlantis development by creating an account on GitHub.

Remember that we are only going to write the API's for these and all interactions to the system will be based on API's. Here is how i approached the problem.

Create the Empty Controller

The first step is to create a restful controller using the scaffolding method. For creating API backend applications do as follows

grails create-app --profile=rest-api

The above command will generate a simple restful application without GSP (Grails server pages). When you run the application using grails run-app you will see that a default page with a json is rendered. Remember that the default generated app will not have a restful controller. There are two ways of generating a controller. One is writing a rest API, and the other is writing a rest API primarily for CURD operation on a resource. We are doing the former, where we write a general API that can do things beyond CURD. To do that we will run the following command

grails create-restful-controller UrlShortner

The above command will generate two files

  1. A restful controller
  2. An associated Integration test.

Now we are ready to implement our logic.

Routing

Once you have the controller ready. You need to setup the routing, the act of mapping an URI to a controller action.  It took some time to understand the conventions, but once done it's pretty straight forward. The Routing is handled via a file called UrlMappings.groovy which will be under the /grails-app/controllers/ folder.  My requirement is to setup two routing entries.

  1. User gives a long URL and asks for a short one.
  2. User gives a short URL and expects it to be redirected to the original URL.

So to achieve the above i added the following two entries

    post "/$controller(.$format)?"(action:"save")
    get "/u/$id"(controller: 'urlEntry', action: 'redirect')
UrlMappings.groovy

The first one is the API endpoint to get a short url given a long url. It basically relies on the ControllerName, save action and the HTTP method POST. So if there is a URL which matches https://localhost:8080/$controllerName with a POST method, then the save() action will be called on the $controllerName. In our case the UrlEntryController will have the save method implemented.

Similarly the second API is the one which does the redirect. Since the redirect URL should be short i hardcoded the /u/ as the route after the base path. This will call the redirect() method in the urlEntry controller.

Now when run, we are able to hit the endpoints and they hit the corresponding action in the respective controller and now the next step is to implement the logic for url shortening and redirecting.

Implementing the Logic and Data Model

+-------+            +-------------+               +---------+                  +---------+
| user  |            | controller  |               | service |                  | domain  |
+-------+            +-------------+               +---------+                  +---------+
    |                       |                           |                            |
    | /post urlEntry        |                           |                            |
    |---------------------->|                           |                            |
    |                       |                           |                            |
    |                       | generateUrl(longUrl)      |                            |
    |                       |-------------------------->|                            |
    |                       |                           |                            |
    |                       |                           | generateUrl                |
    |                       |                           |------------                |
    |                       |                           |           |                |
    |                       |                           |<-----------                |
    |                       |                           |                            |
    |                       |                           | persistGeneratedUrl()      |
    |                       |                           |--------------------------->|
    |                       |                           |                            |
    | /get shortUrl         |                           |                            |
    |---------------------->|                           |                            |
    |                       |                           |                            |
    |                       | getLongUrl(shortUrl)      |                            |
    |                       |-------------------------->|                            |
    |                       |                           |                            |
    |                       |                           | getFromDb(shortUrl)        |
    |                       |                           |--------------------------->|
    |                       |                           |                            |