Ushahidi Platform: Under the Hood (Part 2)

Ushahidi
Jul 22, 2014

This post is the second in a series of articles that will be devoted to explaining the Ushahidi Platform at a technical level for programmers and deployers. In the last blog post, we explored how the architecture of the Ushahidi Platform is radically changing from v2 to v3. In this post, I will detail the different types of objects that make up the new architecture and what their role is within the system. If we refer back to the diagram that illustrates the Clean Architecture, the very center is consists of the Entities, followed by the Use Cases layer, and then the layer of Gateways, Controllers, and Presenters. Diagram of Clean Architecture

Entities

In the Ushahidi Platform, all entities inherit from an abstract base class called Entity, which allows entity objects to define their properties and be constructed from associative arrays. Each entity also includes a Repository interface that defines methods to access that entity type from storage. For example, this is how the User entity is defined today: // < ![CDATA[ // < ![CDATA[ // < ![CDATA[ // ]]> And this is the corresponding UserRepository interface today: // < ![CDATA[ // < ![CDATA[ // < ![CDATA[ // ]]> Every basic entity repository will have a get() method, and because the User entity has two other unique fields, username and email, we can also fetch unique records by those values.

Use Cases

The next layer consists of Use Cases, which are responsible for specifying how various parts of the system interact with each other. Every use case defines its dependencies within the __construct() method and contains an interact() method. For example, this is how we define a use case that registers a new user: // < ![CDATA[ // < ![CDATA[ // < ![CDATA[ // ]]> You might have noticed that the use case is not using UserRepository, but rather a RegisterRepository. This is because every use case also defines the storage interface it needs to persist changes to entities. By separating the interfaces for read access and write access, it is possible to achieve Interface Segregation, one of the five principles that make up SOLID. In this case, the interface currently looks like this: // < ![CDATA[ // < ![CDATA[ // < ![CDATA[ // ]]> In addition to the repository interface, every use also defines its expected input as a Data object, which follows the Data Transfer Object pattern. Data objects are similar to entities in that each object defines its public properties and is constructed from an associative array. The data object for this use case currently looks like: // < ![CDATA[ // < ![CDATA[ // < ![CDATA[ // ]]> Each use case also validates the data object as input by using a Validator object. Currently these validators are implemented at the gateway level (Kohana Framework layer) but this will most likely change in the near future. For now, just know that the validator interface looks like this: // < ![CDATA[ // < ![CDATA[ // < ![CDATA[ // ]]> It is important to note that use cases do not implement any specific logic for validation, authentication, or other concerns. Use cases act only as delegators to other interfaces, which is an example of Single Responsibility.

Gateways and Controllers

Now we can move into the next outer layer, which is currently implemented almost exclusively with the Kohana Framework. This layer is often described as the “delivery layer”, as the code that interacts with the outside world of user input and output lives here. To take user input from this layer and send it into the use case layer, we use objects and interfaces defined within the inner layers, which is an example of Dependency Inversion, one of the five principles that make up SOLID. We also use a Service Locator to gain access to all objects, in order to ensure that all objects can be replaced by other objects that implement the same interfaces. This helps us to achieve Liskov Substitution, as well as Open/Closed programming. In order to create these objects, we use a Parser object to verify that required input has been provided, and then create a data object from that input. Parsers are executed using the magic __invoke() method, which allows an object to be used as a function: // < ![CDATA[ // < ![CDATA[ // < ![CDATA[ // ]]> Note that all the validation that happens here is not validating the input itself, as the parsers are only responsible for ensuring that the required values are provided by the user. Verification of the type and format of the input is handled by the use case. However, because the CSRF security token only matters at the web layer, we do validate it here. This is actually one of the most complex parsers in our system. Most parsers only use not_empty validation rules. Once the input data has been successfully created, it can be handed into the use case, which will respond by providing an entity id, an entity object, or a collection of objects. In the case of user registration, only an entity id is given back. In the case of user registration, we can forward the user from registration directly to login, and reuse the username and password provided to complete a user login.

Final Notes

More often, when use case interactions are happening through the API, the response from a use case would pass through a Formatter to convert the entity, or collection of entities, into a JSON or JSONP response. Finally, we realize that no codebase will ever be absolutely perfect. Although we are constantly striving to fully adapt to SOLID programming, we know that perfection is not attainable. But by constantly re-evaluating design patterns that will allow us to solve problems in creative ways, we hope that the experience of customizing the Ushahidi Platform will be much more enjoyable and consistent than it has been in the past. The next article in this series will focus on the front end web application of the platform and the challenges we experienced in developing it, as well as laying out a plan for what the final UI will look like and the technologies it will be built with. Want to get involved? We can use help with some wishlist features and with general bug reporting. Please read the developer guide wiki page for more information on our current community opportunities and have a look at our workboard on Phabricator. If you just want to chat about the platform or architecture, Woody is often available via IRC, in the #ushahidi and #cleancode channels on irc.freenode.net.