Exploring Object-Relational Mapping with Drogon: A Practical Example
Welcome to this blog post, where we delve into the fascinating realm of Object-Relational Mapping (ORM) using the robust Drogon C++ server framework. Our journey will unravel the mechanics of Drogon’s ORM, allowing us to seamlessly bridge the gap between objects and databases. Let’s begin!
Introducing Drogon
Drogon stands as a beacon of modernity in the realm of C++14/17-based HTTP application frameworks. With an impressive 8k stars on GitHub, Drogon boasts a plethora of advantages, including:
- Enabling a fully asynchronous programming paradigm.
- Facilitating seamless integration with JSON format for requests and responses, a boon for Restful API development.
- Providing support for file upload and download operations.
- Harnessing non-blocking I/O for asynchronous database operations (with PostgreSQL and MySQL/MariaDB).
- Offering a plugin system for effortless integration of additional features via configuration files.
- Embracing C++ coroutines to simplify complex asynchronous code.
Of special interest to us is Drogon’s lightweight and intuitive ORM implementation. This ORM prowess forms the focal point of our exploration.
Setting the Stage
Let’s set the scene for our practical endeavor. Envision a world where interplanetary travel has become an everyday affair. In this context, our database will house the data of a pioneering space travel company. Our database schema comprises four essential entities:
- Planets: Containing details such as
id
,name
, anddistance
from the sun (in millions of km). - Spacecrafts: With attributes like
id
,name
, andcapacity
. - Flights: Comprising
num
,planet_id
, andspacecraft_id
. - Tickets: Encompassing
flight_num
,pax_name
, andprice
.
Here’s a concrete example for each entity:
- Planet Example:
(3, 'Earth', 150)
- Spacecraft Example:
(5, 'Aurora', 10)
- Flight Example:
(1300, 3, 5)
- Ticket Example:
(1300, 'John', 100)
The visual representation of this model takes the form of an Entity Relationship Model (ERM):
The Power of Drogon ORM
Unveiling Drogon ORM
Drogon ORM’s capabilities are extensively documented here.
Code Generation Made Easy
Let’s dive into action. Unlike conventional ORMs that require manual entity configuration and database adaptation, Drogon ORM employs a unique approach. It dynamically generates entity classes based on the database schema. This approach is necessitated by C++’s current lack of a reflection mechanism.
Our database schema resides in the resources/postgres-init.sql
file. Once the database setup is complete, entity classes can be generated using drogon_ctl
. A simple command like drogon_ctl create model model
regenerates files within the ./model
directory. Ensure the model/model.json
file contains accurate configuration if any connection issues arise.
Harnessing Entity Classes
Let’s now witness the prowess of Drogon ORM through the src/SimpleController.cc
file, housing a slew of handlers.
Our objective revolves around three methods:
- Fetching a list of all tickets.
- Retrieving flight information, along with its corresponding destination planet and spacecraft, based on the flight number.
- Adding a new spacecraft to the database.
Fetching All Tickets
Ordinarily, retrieving a simple query result involves considerable effort. For instance, a PostgreSQL query like select * from tickets;
must be executed, followed by the retrieval of results. Drogon ORM simplifies this process significantly:
// ...
auto dbClient = drogon::app().getDbClient();
auto ticketMapper = Mapper<Tickets>(dbClient);
std::vector<Tickets> allTickets = ticketMapper.findAll();
The result? A vector of tickets, precisely as desired!
Comprehensive Flight Information
In scenarios involving complex queries, the burden intensifies. A task like retrieving flight details, associated destination planets, and used spacecrafts might entail multiple queries. For instance:
select * from flights where num = ?;
select s.* from spacecrafts s join flights f on s.id = f.spacecraft_id where f.num = ?;
select p.* from planets p join flights f on p.id = f.planet_id where f.num = ?;
Drogon ORM’s elegance shines through in comparison:
// ...
auto dbClient = drogon::app().getDbClient();
auto flightMapper = Mapper<Flights>(dbClient);
auto flight = flightMapper.findByPrimaryKey(search_flight_num);
flight.getSpacecrafts(dbClient, /* callback */, /* error handling */);
flight.getPlanets(dbClient, /* callback */, /* error handling */);
Note:
getSpacecrafts
adopts plural form due to thespacecrafts
table name.
Adding a New Spacecraft
The contrast remains palpable even when inserting new data. A conventional PostgreSQL insert
query:
insert into spacecrafts values (?, ?, ?);
In the ORM realm, Drogon offers a cleaner, more intuitive solution:
// ...
auto dbClient = drogon::app().getDbClient();
auto spacecraftMapper = Mapper<Spacecrafts>(dbClient);
spacecraftMapper.insert(spacecraftReq, /* callback */, /* error handling */);
A Glimpse of the Rich Toolbox
Explore Drogon ORM’s complete array of methods:
In conclusion, Drogon’s ORM unlocks an avenue of efficiency, simplifying the interaction between objects and databases. As we’ve witnessed through our interplanetary travel database example, Drogon ORM’s code generation and entity mapping streamline database operations, granting developers more time to focus on innovation. Dive into Drogon ORM’s world – a bridge between the realms of C++ and databases, propelling your application development to new heights.