Prevail

A data abstraction library

View project on GitHub

Prevail

Prevail is an abstraction layer for your application data.

It is not an ORM framework, a persistence layer, a networking library or anything complicated.

It simply allows your applications to abstract away the sources of its data. By using Prevail, access to your data becomes asynchronous and event driven. Importantly, the sources of data are decoupled from the logic of your application.

Latest Release:

Boreas 0.2.5 - Javadoc

Gradle Usage

Prevail is published on Sonatype, and is available from Maven Central.

Import the java library with:

dependencies { compile 'ninja.ugly:prevail:0.2.5' }

Alternatively, Android developers can import the Android library directly with just

dependencies { compile 'ninja.ugly:prevail:0.2.5' compile 'ninja.ugly:prevail-android:0.2.5' }

Motivations:

Prevail is motivated by the following points:

  • The majority of code in an application cares little about the source of the data on which it operates. Data should be event driven. When stuff changes, you want to know about it. It's not enough to just query an object. We want to know when that object becomes invalid.
  • Sources of data should be easily changed, without disrupting lots of code. It should be easy to swap a network resource for a hashtable during testing. Sure, that could be achieved by setting up fake test objects, but why bother when it should be easy to swap in new data sources quickly in the first place. Such decoupled code is easier to change in the future too.
  • It should be easy to perform data operations on multiple data sources at once. A local cache query might return quickly, whilst a network query may take longer. However, it should be possible for client code to be completely unaware that there are multiple sources of data. It should be possible for a client merely to handle the data as it arrives.

In particular, on Android:

  • Many people use ContentProviders for data because they give a limited amount of data abstraction, however this can easily contaminate your application with Cursors, Uris, ContentObservers and CursorLoaders. It's frustrating to have code that depends on these classes, when they should really never penetrate into the application's domain. It's much nicer to have all the good stuff, like asynchronous loading and notification of change, within your own classes in your own domain.
  • What happens when you realise that all the stuff you've been persisting to SharedPreferences needs to be in a database so that you can access it with a richer query language? In that case, client code shouldn't have to change. Want to experiment with a NoSQL database, no problem... client code will not care.

Concepts:

The following classes are defined in Prevail:

  1. Chunk. This is the interface to a segment of your data.

    • You implement this to provide data for your application.
    • It implements the relevant CRUD operations on your data, be it a network resource, a database, a file, etc.
    • All operations complete synchronously. That is, they execute on the calling thread and block until completion. The operations return results directly as method return values.
    • It is defined with two generic types, one type is the key by which your data will be accessed, the other type is the value that your Chunk returns. On Android, if you really love ContentProviders, say, then the key could be a Uri and the value could be a Cursor, but these types are completely general and you are free to define the key and value types however you like. For example, the value could be any type in your domain. The key could be as simple as an Integer containing a resource id, or else a String containing a simple query language (eg, "all", "one with id 3"), or some much richer type that defines a query perhaps containing filters, ranges or limits.
    • If your data-model has multiple value types, a Chunk is required for each.
    • If you want to query the same data with different key types, a Chunk is required for each key type.
  2. DataModel. This class is a container for registered Chunks.

    • Each Chunk is registered to a DataModel, optionally by name.
    • Operations are forwarded to a Chunk by name. Optionally, if no name is provided then the operations are forwarded to all registered Chunks of the appropriate types.
    • All operations complete asynchronously. That is, they do not block the calling thread. They return their results in events via an EventDispatcher (See below).
    • All operations return a Future to their result, just in case you do want to block the calling thread. This is often useful when the calling thread is already in the background and it's acceptable to block until the operation completes.
  3. EventDispatcher. This is the interface used to return the results of DataModel operations asynchronously.

    • You implement this to define whatever event distribution mechanism you like. On Android, this could use ContentObservers, if you like Cursors and Uris. This could send Intents to BroadcastReceivers, which might be useful if your data is accessed from outside of your application. Most simply, it could just use an event bus, such as that provided by Google's guava library to send POJOs.
    • Whilst Prevail needs an implementation of EventDispatcher to distribute the results of Chunk operations, the receipt of these results is completely independent of Prevail. If you use Intents, then register BroadcastRecievers as normal. If you use an event bus, then subscribe to the events as normal.
    • All event dispatching should complete asynchronously to the receipt of the events to minimize risk of blocking or recursing on the dispatcher (main) thread.
  4. EventFactory. This is the interface used to produce the required events for distrubution with the EventDispatcher.

    • You define events for one or more of the start, end, progress and error from Chunk operations.
    • These can be set globally on the Chunk, one for each CRUD operation, to distribute events from any such operation. Alternatively, an EventFactory can be passed into each DataModel operation in order to generate one off, bespoke, events for that operation.

Examples:

There are two example applications within the sources:

  1. A Java application manages a list of 'To Do' items. The items are only persisted in memory, but the client code doesn't care, it merely invokes changes and responds to change events.
  2. An Android app that implements a little 'To Do' list of items.
    • The items are persisted to an SQLite3 database but, again, the client code does not care about this.
    • There are controls on the UI for managing the list of items, which simply invoke change and respond to change events.
    • The items are displayed in a ListView that can be managed with or without a Loader.

Roadmap:

Things to do before release of 1.0:

  • Batch update operations and/or transactions.
  • Improved main library javadoc.
  • Android library javadoc.

License:

Prevail is distributed under the MIT License. Please see the license document within the source repository.