Implement todomvc's backend using actframework
JavaScript HTML Shell Java CSS Batchfile
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
src
.gitignore
Procfile
README.md
pom.xml
run_dev
run_prod

README.md

A TodoBackend Implementation in ActFramework

Sample web application implements todo-backend using ActFramework and MongoDB.

Verify the spec compliance at todobackend testing site

View the app on our demo site

To run locally:

//Make sure Mongodb is installed locally
$ mvn clean compile exec:exec

How simple a TodoBackend app can be written in X and Y framework

Just check the source code and table below

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
JavaScript/NodeJs Koa Redis 169
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 core ? (Entity Framework) 887
C#/.Net ASP.NET WebAPI 2 In memory list 215
Swift Kitura MongoDB 473

How can ActFramework make it so clean?

The Model

In this implementation we choose MongoDB as our data persistent store. Act provides awesome integration with Mongodb through act-morphia plugin, which relies on the official Morphia object document mapper layer.

A innovative feature Act brings to developer on top of Morphia is called AdaptiveRecord which allows the backend developer to declare only the fields needs to participate in backend logic. For any fields required by frontend and not used in 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 front end, e.g. title, order and even 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 be persist into our data store, instead it is a derived property that concatenate the GET action URL path and the entity's id. We relies on Morphia's PostLoad and PostPersist lifecycle callback method to init the property.

The Service

It is very unusual to get 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")
    @ResponseContentType(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 one resource type, i.e. the TODO item. Actually it is encouraged because

  1. The operation (service endpoint logic) and the data (entity model) sit together means high cohesion in the app. The design mimic the Object Oriented Programming that encapsulate the data and the operation on the data into a single module.
  2. It improves readability, as we don't need to switch between classes or even packages to find the data model when I am reading the service operating on the data.

And of course we can choose this tight and clean pattern to organize our TODO showcase because Actframework provides the great flexibility to allow it put the action handler into any place given the action annotation is presented.

BTW, we can even take out the @Produces(H.MediaType.JSON) line if the test spec accepted and fixed this issue

Where is the code support CORS

It is required by TodoBackend that showcase application must enable CORS. Thus we see the code like the following:

From Java 8 with Spring 4 Boot

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 from Vert.x and PostgreSQL

// CORS enabling
router.route().handler(CorsHandler.create("*")
        .allowedMethod(HttpMethod.GET)
        .allowedMethod(HttpMethod.POST)
        .allowedMethod(HttpMethod.OPTIONS)
        .allowedMethod(HttpMethod.DELETE)
        .allowedMethod(HttpMethod.PATCH)
        .allowedHeader("X-PINGARUNER")
        .allowedHeader("Content-Type"));

Or from Java with Dropwizard

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 code in the ActFramework implementation. The only thing we've done is add cors=true in the config file This is another cool stuff about 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 support creating RESTful service in a much simpler way. With ActFramework the developer just need to focus on business logic, not plumbing.