The structure of this project is heavily influenced by the clean architecture:
- A
coremodule where we define the domain entities and the functionalities (also known as uses cases, business rules, etc.). They do not know that this application has a web interface or that data is stored in relational databases. - A
repositoriesmodule that knows how to store domain entities in a relational database. - A
deliverymodule that knows how to expose in the Web the functionalities. - An
appmodule that contains the main, the configuration (i.e. it linkscore,deliveryandrepositories), and the static assets (i.e. html files, JavaScript files, etc. )
Usually, if you plan to add a new feature, usually:
- You will add a new use case to the
coremodule. - If required, you will modify the persistence model in the
repositoriesmodule. - You will implement a web-oriented solution to expose to clients in the
deliverymodule.
Sometimes, your feature will not be as simple, and it would require:
- To connect a third party (e.g. an external server).
In this case you will add a new module named
gatewayresponsible for such task. - An additional application.
In this case you can create a new application module (e.g.app2) with the appropriate configuration to run this second server.
Features that require the connection to a third party or having more than a single app will be rewarded.
The application can be run as follows:
./gradlew :app:bootRunNow you have a shortener service running at port 8080. You can test that it works as follows:
$ curl -v -d "url=http://www.unizar.es/" http://localhost:8080/api/link
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST /api/link HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.71.1
> Accept: */*
> Content-Length: 25
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 25 out of 25 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 201
< Location: http://localhost:8080/tiny-6bb9db44
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Tue, 28 Sep 2021 17:06:01 GMT
<
* Connection #0 to host localhost left intact
{"url":"http://localhost:8080/tiny-6bb9db44","properties":{"safe":true}}% And now, we can navigate to the shortened URL.
$ curl -v http://localhost:8080/tiny-6bb9db44
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /tiny-6bb9db44 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 307
< Location: http://www.unizar.es/
< Content-Length: 0
< Date: Tue, 28 Sep 2021 17:07:34 GMT
<
* Connection #0 to host localhost left intactThe uberjar can be built and then run with:
./gradlew build
java -jar app/build/libs/app.jarThe project offers a minimum set of functionalities:
-
Create a short URL. See in
corethe use caseCreateShortUrlUseCaseand indeliverythe REST controllerUrlShortenerController. -
Redirect to a URL. See in
corethe use caseRedirectUseCaseand indeliverythe REST controllerUrlShortenerController. -
Log redirects. See in
corethe use caseLogClickUseCaseand indeliverythe REST controllerUrlShortenerController.
The objects in the domain are:
ShortUrl: the minimum information about a short urlRedirection: the remote URI and the redirection modeShortUrlProperties: a handy way to extend data about a short urlClick: the minimum data captured when a redirection is loggedClickProperties: a handy way to extend data about a click
The above functionality is available through a simple API:
POST /api/linkwhich creates a short URL from data send by a form.GET /tiny-{id}whereididentifies the short url, deals with redirects, and logs use (i.e. clicks).
In addition, GET / returns the landing page of the system.
All the data is stored in a relational database. There are only two tables.
- shorturl that represents short url and encodes in each row
ShortUrlrelated data - click that represents clicks and encodes in each row
Clickrelated data
For further reference, please consider the following sections:
The following guides illustrate how to use some features concretely: