Concept

A DataSource is an interace for those classes responsible of managing sources of data.

Among the Harmony architecture, the data source is the first component that provides a defined public interface for data manipulation. Every action that a data source does must be bundled within its interface.

Data Sources are splitted in three groups taking into account the action to be done:

  • Get is the responsible of all actions that fetch data from external sources
  • Put is the responsible of all actions that modify and push data to external sources
  • Delete is the responsible of all action that delete data from external sources

Data sources can accomplish many different things and can adapt to your project requirements and necessities. For example, from storing/fetching data in a local databases, to send data via an HTTP/Socket client or any third party services.

In an effort to make data sources decoupled from the requirements of external sources, data sources use the concept of Query: an object that itself intrinsically defines how data must be manipulated, containing all attributes required to execute the action.

For more information, read the Query reference.

Understanding the abstraction

A good example of how a data source is organized is to think on how would you do a data source class without Harmony.

Typically, you would have a singleton class containing a list of methods for all the actions your system need to support. For example, a typical network class listing all the HTTP requests.

class UserNetworkAPIService {
func login(username, password): User
func fetchUserDetails(id): User
func updateProfilePicture(url, userId): User
}

In Harmony, instead of having this class listing all methods, we would create a data source grouping all methods withing the Get, Put and Delete actions and translating each method with its parameters into a query with its attributes.

class IdQuery(id)
class LoginQuery(username, password)
class UpdateProfilePictureQuery(url, userId)
class UserNetworkDataSource: GetDataSource<User>, PutDataSource<User> {
func get(query): User {
if (query istypeof IdQuery) {
// fetch user details and return
}
}
func put(user, query): User {
if (query istypeof LoginQuery) {
// perform login
} else if (query istypeof UpdateProfilePictureQuery) {
// udpate user profile picture
}
}
}

By normalizing how we interface with data sources, we can start building complex compositions of data sources top of it, which is the foundations of Harmony.

Interfaces

Find below the interfaces for each data source group:

GET

interface GetDataSource<V> : DataSource {
fun get(query: Query): Future<V>
fun getAll(query: Query): Future<List<V>>
}

PUT

PUT methods will be responsible of handling any editing, modifying, sending or operating action on the data.

Some examples:

  • Creating a book
  • Editing a user profile
  • Liking a picture
  • Sending a push notification
interface PutDataSource<V> : DataSource {
fun put(query: Query, value: V?): Future<V>
fun putAll(query: Query, value: List<V>? = emptyList()): Future<List<V>>
}
Note

Note that in the put function, the value is optional. This happens becasue it is not always required to have an actual value to perform the action defined by the Query. In the case of putAll, an empty array can be passed.

DELETE

On delete methods, only a Query is required and no value is returned rather than a promise encapsulating the output error.

Also, there is only one delete method (no deleteAll) as it is considered an atomic action on its own, without distinctions of if deleting one or many.

interface DeleteDataSource : DataSource {
fun delete(query: Query): Future<Unit>
}

Extensions

Not all Harmony languages are capable of supporting all extensions. Find below the list of all extensions by supported platform.

Key access

Instead of using IdQuery to interface with data sources, there are extensions to syntax sugar the creation of IdQuery.

This means that instead of calling a data source with a query new IdQuery('my-key'), it can be used directly the my-key identifier.

suspend fun <K, V> GetDataSource<V>.get(id: K): V = get(IdQuery(id))
suspend fun <K, V> GetDataSource<V>.getAll(ids: List<K>): List<V> = getAll(IdsQuery(ids))
suspend fun <K, V> PutDataSource<V>.put(id: K, value: V?): V = put(IdQuery(id), value)
suspend fun <K, V> PutDataSource<V>.putAll(ids: List<K>, values: List<V>?) = putAll(IdsQuery(ids), values)
suspend fun <K> DeleteDataSource.delete(id: K) = delete(IdQuery(id))

Find below examples by platform:

// Instead of:
dataSource.get(IdQuery("myKey"))
dataSource.put(IdQuery("myKey"), object)
dataSource.delete(IdQuery("myKey"))
// Use:
dataSource.get("myKey")
dataSource.put("myKey", object)
dataSource.delete("myKey")

Default Implementations

Harmony provides multiple default implementations.

Find below a list of the most common ones: