Persisting Data/State

I was originally going to start writing about Data Access, and how it should be abstracted and encapsulated so if the database changes, it’s easier to replace the code that accesses it. However, after doing some research, I read about the term “Persistence”. By using Persistence we are expanding the reach of the application, it may no longer be data that it’s storing, but the state. The state of the application can be linked to data or infrastructure, basically, anything that outlives the execution of the application.

It is important to understand that state can be stored in many different ways: SQL databases, No-SQL databases, infrastructure components, network files, cloud storage, another API… you get the idea. So, by using Persistence and state (vs Data Access and Data) we have a clearer view of what can be achieved.

A Persistence layer can have up to four actions: Create, Read, Update, Delete, or CRUD for short. Sometimes, the state cannot be altered, so the only action that can be taken is Read; other times, we can’t delete something from the state either. It really depends on what we are interacting with.

Having this in mind, having an interface that defines how the application can interact with the state is vital. With the interface defined we have great advantages:

  • Abstraction
  • Dependency Injection
  • Unit Tests

Abstraction

Abstraction means that whatever the calling code is doing, it doesn’t really care how it’s done, it just needs to know what can be done. Instead of using a concrete persistence object that glues the client to the persistence layer, it’s better to use an interface. By doing so, the persistence layer can change, and, as long as it uses the same interface, the client is not affected by this change.

Imagine if we have an application that turns the lights on or off in a home. The lights are in the Persistence layer, let’s call it LightingService, and have two states: On and Off. Our application can call the Persistence layer to turn off the lights, then it can call it again to turn the lights on.

Now, we want to add sprinklers to our home, sprinkles have similar functionality as lights: On or off, however, the process to toggle them is different than the lights. This new SprinklerService has the same functionality as the LightingService.

So, If we implement an interface called IToggleHomeDevice that is shared between the LightingService and the SprinklerService, we can make both follow the same rules of interaction, and our application would call functions defined in IToggleHomeDevice. This way, the application is even more configuration, it can have a button to turn on or off a device, but it doesn’t really care what it is, making our code more extensible

Dependency Injection

This means that the client doesn’t need to instantiate anything that it has a dependency, dependencies are injected when creating the client. Since we are coding against an abstraction, we can make that abstraction be what we need when we need it.

For example, when working on a Xamarin Forms app for Contact Managing for both Android and iOS, you want to have shared code as much as possible, but both OSs interact differently with their devices, in this example, User Contacts. To prevent duplicate code, you specify an interface to interact with contacts, IContactsService, then on each device project, you implement that interface with device-specific code; then on your shared project, the device-specific code will be injected when we need to interact with Contacts, AndroidContactService or iOSContactService, but as far as the shared code is concerned it still only uses IContactService.

Because Unit Tests should be repeatable and have no Persistence dependencies, we can inject a mock object so we can properly unit test. This Mock object will fake the return values we expect, so we can have a repeatable test without having to reach out to the actual persistence layer.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *