Slim3

Home

Slim3 is a full-stack MVC framework optimized for Google App Engine/Java, and you can use Slim3 as just a datastore framework, too.

Our main concept is "Simple" and "Less Is More". "Less is more" means simplicity and clarity lead to good design.

80% of the effects come from 20% of the causes. This is known as The Pareto Principle. We concentrate all our energies into 20% of the causes. Programmers can resolve the other issues without framework constraints.

We want to get rid of complex and bloated features from your applications and our framework. As a result, we will slim down.

Slim3 is an open source framework. The license is Apache License, Version 2.0.

The main features of Slim3 are as follows:


Global Transactions

App Engine supports transactions for just only a single entity group. When you need to update multiple entity groups consistently, you must have a hard time of it. Good news for you. Slim3 supports global transactions between multiple entity groups.

The usual example of the need for global transactions - so common that it's practically canonical - is the 'bank account' example. Suppose you have a set of accounts, defined something like this:

Account

@Model
public class Account {

    @Attribute(primaryKey = true)
    private Key key;

    private Integer balance;
    ...
}

Naturally, you need to be able to transfer funds between accounts; those transfers need to be transactional, or you risk losing peoples' money, or worse (from a bank's point of view) duplicating it! Each account is assigned to a different entity group for avoiding a high rate of contention, so it is impossible to transfer money in a local transaction.

Fortunately, we can make it possible to do transactional transfers between accounts using global transactions as follows:

public void tansferFunds(Key srcKey, Key destKey, Integer amount) {
GlobalTransaction gtx = Datastore.beginGlobalTransaction();
Acount src = gtx.get(Acount.class, srcKey);
Acount dest = gtx.get(Acount.class, destKey);
if (src.getBalance() >= amount) {
src.setBalance(src.getBalance() - amount);
dest.setBalance(dest.getBalance() + amount);
} else {
throw new IllegalStateException(...);
}
gtx.put(src, dest);
gtx.commit();
}

You may worry about the overhead of global transactions. Don't worry. It is not very expensive.
We prepare the demonstration.

Local transaction test code

long start = System.currentTimeMillis();
for (int i = 0; i < entityGroups; i++) {
   Transaction tx = Datastore.beginTransaction();
   Datastore.put(tx, new Entity("Hoge"));
   tx.commit();
}
long time = System.currentTimeMillis() - start;

Global transaction test code

start = System.currentTimeMillis();
GlobalTransaction gtx = Datastore.
beginGlobalTransaction();
for (int i = 0; i < entityGroups; i++) {
   gtx.put(new Entity("Hoge"));
}
gtx.commit();
time = System.currentTimeMillis() - start;

The result:
Entity Groups Local Transaction(millis) Global Transaction(millis)
1 67 70
2 450 415
3 213 539
4 1498 981
5 447 781

You can try this demonstration:
http://slim3demo.appspot.com/gtx/

See Global Transactions.


Faster than JDO/JPA

Slim3 Datastore is a thin wrapper of Datastore Low level API. Low level API is faster than JDO/JPA, because it is specialized for Bigtable.

Slim3 Datastore creates mapping logics between a entity and a model as the source code in a meta data of a model. The meta data is created by Annotation Processing Tool automatically when compiling.

The following code is a part of BarMeta:

@Override
public Bar entityToModel(Entity entity) {
    Bar model = new Bar();
    model.setKey(entity.getKey());
    model.setSortValue((String) entity.getProperty("sortValue"));
    return model;
}

@Override
public Entity modelToEntity(Object model) {
    Bar m = (Bar) model;
    Entity entity = null;
    if (m.getKey() != null) {
        entity = new Entity(m.getKey());
    } else {
        entity = new Entity("Bar");
    }
    entity.setProperty("sortValue", m.getSortValue());
    return entity;
}

Slim3 Datastore does not need runtime reflection for mapping between a entity and a model, so Slim3 Datastore is faster than JDO.
To retrieve 10,000 simple entities, Slim3 is around 3 times faster than JDO.
Try online demonstration.


Fast spin-up

If an app is idle, App Engine spins down it a short time(2 or 3 minutes) later and will spin up a new instance when a request comes in for it. Also, when the load increases, App Engine will spin up a new instance. So fast spin-up is very important when you use App Engine.

Slim3 does not use PersistenceManagerFactory/EntityManagerFactory which is known as a slow starter and avoids extra initializations carefully, so Slim3 spins up fast. It takes around 1100 cpu_ms.


HOT reloading

Slim3 supports HOT reloading. HOT reloading means a new version of a class is automatically reloaded on the fly. Due to HOT reloading, when you change the source code, you can see the latest result on your browser without restarting web application.


Type safe query

Slim3 supports a type-safe query as follows:

EmployeeMeta e = EmployeeMeta.get();
List<Employee> list = Datastore.query(e)
    .filter(e.salary.greaterThan(5000), e.job.equal("ANALYST"))
    .sort(e.salary.asc)
    .asList();

A meta data of a model like EmployeeMeta is created by Annotation Processing Tool automatically.

An equivalent JDO query is as follows:
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
    Query query = pm.newQuery(Employee.class);
    query.setFilter("salary > :salary && job == :job");
    query.setOrdering("salary asc");
    List<Employee> list = (List<Employee>) query.execute(5000, "ANALYST");
} finally {
    pm.close();
}


If you use a query based on string, a compiler can not report an error when refactoring the property name.
If you use a type-safe query, when you rename "salary" property, a compiler will tell you "salary" no longer exists, because the meta data of the model is recreated by Annotation Processing Tool when refactoring.