How Simple a Todo Backend App Can Be
Learn how to use one of the newer frameworks in the Todo-Backend project, and significantly decrease your lines of code while not skimping on the quality of your app.
Join the DZone community and get the full member experience.
Join For FreeThe Todo-Backend project provides an arena to showcase backend tech stacks. It defines a simple web API in the form of a to-do list and users can create their own identical APIs using various tech stacks.
As for now, there are over 80 Todo-Backend projects implemented in a wide range of tech/framework combinations, including JVM (Java/Scala/Kotlin/Closure), C#/.Net, Ruby, Python, PHP, Golang, Haskell, and a lot more. On each language/platform, there are one or more implementations based on different framework/data persist solution combinations.
I am the author of ActFramework, and just finished the Act implementation of Todo-Backend. As ActFramework is designed to boost developer productivity and enable express functions in less code without sacrificing readability, I was curious to see how Act's implementation compared to other implementations in terms of expressiveness, or simply speaking, the line of code. I did my homework to get the LOC data from some commonly used tech stacks and compiled the result into the following table:
Language/Platform | Implementation | Data Persistent | Line of Code |
---|---|---|---|
Java/JVM | ActFramework | MongoDB | 64 |
Java/JVM | Spring4 + Boot | Java Set | 200 |
Java/JVM | vertx | MongoDB | 241 |
Java/JVM | Dropwizard | Java Map | 115 |
Java/JVM | Jooby | Java Map | 231 |
Java/JVM | SparkFramework | PostgreSQL | 348 |
Kotlin/JVM | Rapidoid | Java Map | 81 |
Closure/JVM | Closure | PostgreSql | 142 |
Scala/JVM | Scala/Play2.5 | PostgreSql | 136 |
Golang | Gin | Map in memory | 128 |
Golang | stdlib | In memory data structure | 238 |
JavaScript/NodeJs | express | PostgreSql | 130 |
Python | webpy | Array in memory | 32 |
Python | django | sqllite | 164 |
Ruby | rails | PostgreSql | 311 |
PHP | symfony2 | sqlite | 130 |
Haskell | Snap | Sqlite | 98 |
C#/.Net | Asp.Net Web API | In memory list | 215 |
C#/.Net | Asp.Net core | ? (Entity Framework) | 887 |
Swift | Kitura | MongoDB | 473 |
Obviously, Act's implementation is very concise in comparison to other implementations, as it uses only 64 lines of code. Consider that normal Java implementations take up to 200 lines of code and that Act in the Java language was implemented in Dropwizard and used a HashMap as a data persist, which nearly doubled the size of Act's codebase - and it still had only 64 lines of code!
How ActFramework Can Make it So Compact
This is a simple project consisting of a model and a service (AKA controller). We will check the source code of these components to understand Act's implementation.
1. The Model
We chose MongoDB as our data persistent store. Act provides awesome integration with MongoDB through the act-morphia plugin, which relies on the official Morphia object document mapper layer.
An innovative feature Act brings to developers on top of Morphia is called AdaptiveRecord
which allows the backend developer to declare only the fields needed to participate in backend logic. For any fields required by frontend and not used in a Java app, just ignore them. Thus here is our entity model class for Todo
:
@Entity(value = "todo", noClassnameStored = true)
public class Todo extends MorphiaAdaptiveRecordWithLongId<Todo> {
// needs to define this property to make it comply with todobackend spec
// unless https://github.com/TodoBackend/todo-backend-js-spec/issues/6
// is accepted
public boolean completed;
// url is required as per backend test spec. However it is not required
// to put into the database. So we mark it as Transient property
@Transient
public String url;
// We will generate the derived property `url` after
// saving the model and loading the model
@PostLoad
@PostPersist
private void updateUrl() {
url = Act.app().router().fullUrl(S.concat("/todo/", getIdAsStr()));
}
}
We don't need to declare all fields presented on the front end, e.g. title
, order
and completed
, which is declared in the source code because of this issue in the test spec.
Note the url
is not part of the data to persist into our data store, instead, it is a derived property that concatenates the GET action URL path and the entity's id. We rely on Morphia's PostLoad
and PostPersist
lifecycle callback method to init the property.
2. The Service
It is very unusual to get a service class nested into the entity model class like what we did in this implementation:
@Entity(value = "todo", noClassnameStored = true)
public class Todo extends MorphiaAdaptiveRecordWithLongId<Todo> {
// needs to define this property to make it comply with todobackend spec
// unless https://github.com/TodoBackend/todo-backend-js-spec/issues/6
// is accepted
public boolean completed;
....
@Controller("/todo")
@Produces(H.MediaType.JSON)
public static class Service extends MorphiaDaoWithLongId<Todo> {
@PostAction
public Todo create(Todo todo, Router router) {
return save(todo);
}
@GetAction("{id}")
public Todo show(long id) {
return findById(id);
}
...
}
}
However, in the TODO application, we think it is not a bad choice, because our TODO service only operates on a single resource type, i.e. the TODO item. Actually, it is encouraged in this case because:
The operation (service endpoint logic) and the data (entity model) sit together, meaning high cohesion in the app. The design mimics the Object Oriented Programming that encapsulates the data and the operation on the data into a single module.
It improves readability, as we don't need to switch between classes or even packages to find the data model when reading the service that manipulates the data.
Also, we can even take out the @Produces(H.MediaType.JSON)
line if the test spec accepted and fixed this issue.
3. CORS Code
It is required by Todo-Backend that a showcase application must enable CORS. Thus we see code like:
Java 8 with Spring 4 Boot implementation
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, origin, content-type, accept");
chain.doFilter(req, res);
}
Or Java with Dropwizard implementation
private void addCorsHeader(Environment environment) {
FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
filter.setInitParameter("allowedOrigins", "*");
filter.setInitParameter("allowedMethods", "GET,PUT,POST,DELETE,OPTIONS,HEAD,PATCH");
}
However, we don't see any of these CORS relevant codes in the ActFramework implementation. The only thing we've done is added cors=true
into the config file. This is another cool aspect of Act, it integrates utilities supporting common features in a web app including CORS, CSRF, etc.
In summary, ActFramework provides a flexible and powerful infrastructure that supports creating RESTful service in a much simpler way. With ActFramework, the developer just needs to focus on business logic, not plumbing.
Opinions expressed by DZone contributors are their own.
Comments