When data access is the easiest part of a microservice

Pt. 5 of a series on the KillrVideo Python project

Jeff Carpenter
Feb 21 · 7 min read

Business logic? What business logic?

As it turns out, there’s really not a lot of business logic to speak of in the KillrVideo service tier. A previous version of the system that allowed uploading of actual video files via an Uploads Service certainly had more interesting business logic.

  • converting a password to a md5 hashed value, and comparing hashed password strings to support user login, as in the User Management Service

Data access code — no sweat! (mostly)

As I mentioned in the previous post, the process I followed for the Python services was to implement service operations in the order implied by the killrvideo-integration-tests, starting with the User Management Service. I’ll walk you through how the User Management Service was implemented, as the same basic pattern was followed for the other services.

Installing the driver

The first step is to install the DataStax Enterprise Python Driver:

pip install dse-driver

Initializing Cluster and Session objects

In order to read and write data to Cassandra (DSE), we’ll need to initialize the driver establish connections to the cluster. We do this through the Cluster and Session classes provided by the driver, as you can read about on the Getting Started page of the documentation.

from dse.cluster import Cluster, ExecutionProfile, EXEC_PROFILE_DEFAULT
from dse import ConsistencyLevel
import dse.cqlengine.connection
def serve():

file = open('config.json', 'r')
config = json.load(file)
contact_points = config['CONTACT_POINTS']
default_consistency_level = config['DEFAULT_CONSISTENCY_LEVEL']

# Wait for Cassandra (DSE) to be up, aka registered in etcd

# Initialize Cassandra Driver and Mapper
profile = ExecutionProfile(consistency_level =
ConsistencyLevel.name_to_value[default_consistency_level])
cluster = Cluster(contact_points=contact_points,
execution_profiles={EXEC_PROFILE_DEFAULT: profile})
session = cluster.connect("killrvideo")
dse.cqlengine.connection.set_session(session)
{
"CONTACT_POINTS": ["10.0.75.1"],
"DEFAULT_CONSISTENCY_LEVEL": "LOCAL_QUORUM"
}
profile = ExecutionProfile(consistency_level = 
ConsistencyLevel[default_consistency_level])
cluster = Cluster(contact_points=contact_points, 
execution_profiles={EXEC_PROFILE_DEFAULT: profile})
session = cluster.connect("killrvideo")

Creating mapper classes

In order to use the mapper, we’re going to need some classes that define the types that will be mapped to our Cassandra tables. In keeping with the principle of modeling Cassandra tables around our application queries, there are two tables used to store data, which have been designed to support queries we’ll discuss more below.

from dse.cqlengine import columns
from dse.cqlengine.models import Model
class UserModel(Model):
"""Model class that maps to the user table"""
__table_name__ = 'users'
user_id = columns.UUID(db_field='userid', primary_key=True)
first_name = columns.Text(db_field='firstname')
last_name = columns.Text(db_field='lastname')
email = columns.Text()
created_date = columns.Date()


class UserCredentialsModel(Model):
"""Model class that maps to the user_credentials table"""
__table_name__ = 'user_credentials'
email = columns.Text(primary_key=True)
user_id = columns.UUID(db_field='userid')
password = columns.Text()

Inserting data using the mapper

Following the integration test order, the first operation to implement in the User Management Service is create_user. Note that we need to insert into two different tables to support our access patterns:

# insert into user_credentials table first so we can ensure uniqueness with LWT
try:
UserCredentialsModel.if_not_exists().create(user_id=user_id, email=email, password=hashed_password)
except LWTException:
# Exact string in this message is expected by integration test
raise ValueError('Exception creating user because it already exists for ' + email)

# insert into users table
UserModel.create(user_id=user_id, first_name=first_name, last_name=last_name, email=email)

Retrieving data using the mapper

Next it’s time to implement the methods for retrieving user account data. The first query our application requires is used to support login in the verify_credentials operation:

# retrieve the credentials for provided email from user_credentials table
user_credentials = UserCredentialsModel.get(email=email)
# filter().all() returns a ModelQuerySet, we iterate over the query set to get the Model instances
user_results = UserModel.filter(user_id__in=user_ids).all()
users = list()
for user in user_results:
users.append(user)
return users
SELECT * FROM killrvideo.users WHERE user_id IN <id1>, <id2>...

Next time: more complex data access cases

So, that was pretty easy, right? If you’re suspicious, you’re right, there are some cases where the “easy way” doesn’t work.

Quick Code

Find the best tutorials and courses for the web, mobile, chatbot, AR/VR development, database management, data science, web design and cryptocurrency. Practice in JavaScript, Java, Python, R, Android, Swift, Objective-C, React, Node Js, Ember, C++, SQL & more.

Jeff Carpenter

Written by

Developer Advocate helping you succeed with Apache Cassandra / DataStax Enterprise, cloud architecture, and distributed systems. Opinions are my own.

Quick Code

Find the best tutorials and courses for the web, mobile, chatbot, AR/VR development, database management, data science, web design and cryptocurrency. Practice in JavaScript, Java, Python, R, Android, Swift, Objective-C, React, Node Js, Ember, C++, SQL & more.