Ce contenu traduit automatiquement peut être modifié par les membres de la communauté. Nous vous invitons à améliorer la traduction en cliquant sur le lien « Modifier » associé aux phrases ci-dessous.
The Working Programmer
Cassandra NoSQL Database, Part 2: Programming
Ted Neward
In my August 2012 column, “Cassandra NoSQL Database: Getting Started,” I examined Apache Cassandra.
It’s described as the “open source, distributed, decentralized, elastically scalable, highly available, fault-tolerant, tuneably consistent, column-oriented database that bases its distribution design on Amazon Dynamo and its data model on Google Bigtable” in the book, “Cassandra: The Definitive Guide” (O’Reilly Media, 2010).
To be more precise, I looked at how to install Cassandra (which, because it’s a Java-based database, also required getting a Java Virtual Machine up and running on your machine if you didn’t have one already), how to connect to it from the command line and what its data model looked like.
The data model bears repeating because it’s quite noticeably different in structure than the relational database with which most developers are familiar.
As I discussed last time (msdn.microsoft.com/magazine/JJ553519), Cassandra is a “column-oriented” data store, which means that instead of storing identically structured tuples of data arranged according to a fixed structure (the table schema), Cassandra stores “column families” in “keyspaces.” In more descriptive terms, Cassandra associates a key value with a varying number of name/value pairs (columns) that might be entirely different from one “row” to another.
For example, consider the keyspace “Earth” I created last time, with a column family named “People,” into which I’ll write rows that (may or may not) look like this:
RowKey: tedneward
ColumnName:"FirstName", ColumnValue:"Ted"
ColumnName:"LastName", ColumnValue:"Neward"
ColumnName:"Age", ColumnValue:41
ColumnName:"Title", ColumnValue:"Architect"
RowKey: rickgaribay
ColumnName:"FirstName", ColumnValue:"Rick"
ColumnName:"LastName", ColumnValue:"Garibay"
RowKey: theartistformerlyknownasprince
ColumnName:"Identifier", ColumnValue: <image>
ColumnName:"Title", ColumnValue:"Rock Star"
As you can see, each “row” contains conceptually similar data, but not all “rows” will have the same data, depending on what the developer or business needed to store for any particular row key.
I don’t know Rick’s age, so I couldn’t store it.
In a relational database, if the schema mandated that age was a non-NULLABLE column, I couldn’t have stored Rick at all.
Cassandra says, “Why not?”
My previous column demonstrated inserting and removing data from the command line, but this isn’t particularly helpful if the goal is to write applications that will access and store data.
So, without further background, let’s dive into what it takes to write applications that read from and store to Cassandra.
Cassandra, O Cassandra, Wherefore Art Thou Cassandra?
To start, I need to connect to Cassandra from the Microsoft .NET Framework.
Doing so involves one of two techniques: I can use the native Apache Thrift API, or I can use a third-party wrapper on top of the native Thrift API.
Thrift is a binary remote procedure call toolkit, similar in many ways to DCOM (bet you haven’t thought of that in a few years) or CORBA or .NET Remoting.
It’s a particularly low-level approach to communicating with Cassandra, and while Thrift has C# support, it’s not trivial to get all that up and running.
Alternatives to Thrift include FluentCassandra, cassandra-sharp, Cassandraemon and Aquiles (the Spanish translation of Achilles, which keeps the ancient Greek theme alive and well).
All of these are open source and offer some nicer abstractions over the Cassandra API.
For this column, I’m going to use FluentCassandra, but any of them seem to work pretty well, the odd Internet flame war notwithstanding.
FluentCassandra is available as a NuGet package, so the easiest way to get started is to fire up the NuGet Package Manager in a Visual Studio Test project (so I can write exploration tests) and do an “Install-Package FluentCassandra.” (The most recent version as of this writing is 1.1.0.) Once that’s done, and I’ve double-checked that the Cassandra server is still running after I toyed with it for the August column, I can write the first exploration test: connecting to the server.
FluentCassandra lives in the namespace “FluentCassandra” and two nested namespaces (“Connections” and “Types”), so I’ll bring those in, and then write a test to see about connecting to the database:
private static readonly Server Server =
new Server("localhost");
TestMethod]
public void CanIConnectToCassandra()
{
using (var db = new CassandraContext(keyspace: "system",
server:Server))
{
var version = db.DescribeVersion();
Assert.IsNotNull(version);
testContextInstance.WriteLine("Version = {0}", version);
Assert.AreEqual("19.30.0", version);
}
}
Note that by the time you read this, it’s possible that the version number will be different from when I wrote it, so if that second assertion fails, check the output window to see the returned string.
(Remember, exploration tests are about testing your understanding of the API, so writing output isn’t as much of a bad idea as it is in an automated unit test.)
The CassandraContext class has five different overloads for connecting to a running Cassandra server, all of them pretty easy to infer—they all deal with connection information of one form or another.
In this particular case, because I haven’t created the keyspace in which I want to store (and later read) the data, I’m connecting to the “system” keyspace, which is used by Cassandra to store various systemic details in much the same way that most relational databases have one instance reserved for database metadata and security and such.
But this means I don’t want to write to that system keyspace; I want to create my own, which forms the next exploration test, as shown in Figure 1.
Figure 1 Creating a System Keyspace
[TestMethod]
public void DoesMyKeyspaceExistAndCreateItIfItDoesnt()
{
using (var db = new CassandraContext(keyspace: "system",
server:Server))
{
bool foundEarth = false;
foreach (CassandraKeyspace keyspace in db.DescribeKeyspaces())
{
Apache.Cassandra.KsDef def = keyspace.GetDescription();
if (def.Name == "Earth")
foundEarth = true;
}
if (!foundEarth)
{
var keyspace = new CassandraKeyspace(new
CassandraKeyspaceSchema
{
Name = "Earth"
}, db);
keyspace.TryCreateSelf();
}
Assert.IsTrue(db.KeyspaceExists("Earth"));
}
}
Admittedly, the loop through all the keyspaces in the database is unnecessary—I do it here to demonstrate that there are places in the FluentCassandra API where the underlying Thrift-based API peeks through, and the “Apache.Cassandra.KsDef” type is one of those.
Now that I have a keyspace, I need at least one column family within that keyspace.
The easiest way to create this uses Cassandra Query Language (CQL), a vaguely SQL-like language, as shown in Figure 2.
Figure 2 Creating a Column Family Using Cassandra Query Language
[TestMethod]
public void CreateAColumnFamily()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
CassandraColumnFamily cf = db.GetColumnFamily("People");
if (cf == null)
{
db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
KEY ascii PRIMARY KEY,
FirstName text,
LastName text,
Age int,
Title text
);");
}
cf = db.GetColumnFamily("People");
Assert.IsNotNull(cf);
}
}
The danger of CQL is that its deliberately SQL-like grammar combines with the easy misperception that “Cassandra has columns, therefore it must have tables like a relational database” to trick the unwary developer into thinking in relational terms.
This leads to conceptual assumptions that are wildly wrong.
Consider, for example, the columns in Figure 2.
In a relational database, only those five columns would be allowed in this column family.
In Cassandra, those are just “guidelines” (in a quaintly “Pirates of the Caribbean” sort of way).
But, the alternative (to not use CQL at all) is less attractive by far: Cassandra offers the API TryCreateColumnFamily (not shown), but no matter how many times I try to wrap my head around it, this still feels more clunky and confusing than the CQL approach.
‘Data, Data, Data!
I Cannot Make Bricks Without Clay!’
Once the column family is in place, the real power of the FluentCassandra API emerges as I store some objects into the database, as shown in Figure 3.
Figure 3 Storing Objects in the Database
[TestMethod]
public void StoreSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
db.Attach(tedneward);
db.SaveChanges();
Assert.IsNull(db.LastError);
}
}
Notice the use of the “dynamic” facilities of C# 4.0 to reinforce the idea that the column family is not a strictly typed collection of name/value pairs.
This allows the C# code to reflect the nature of the column-oriented data store.
I can see this when I store a few more people into the keyspace, as shown in Figure 4.
Figure 4 Storing More People in the Keyspace
[TestMethod]
public void StoreSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
rickgaribay.FirstName = "Rick";
rickgaribay.LastName = "Garibay";
rickgaribay.HomeTown = "Phoenix";
dynamic theArtistFormerlyKnownAsPrince =
peopleCF.CreateRecord("TAFKAP");
theArtistFormerlyKnownAsPrince.Title = "Rock Star";
db.Attach(tedneward);
db.Attach(rickgaribay);
db.Attach(theArtistFormerlyKnownAsPrince);
db.SaveChanges();
Assert.IsNull(db.LastError);
}
}
Again, just to drive the point home, notice how Rick has a HomeTown column, which wasn’t specified in the earlier description of this column family.
This is completely acceptable, and quite common.
Also notice that the FluentCassandra API offers the “LastError” property, which contains a reference to the last exception thrown out of the database.
This can be useful to check when the state of the database isn’t known already (such as when returning out of a set of calls that might have eaten the exception thrown, or if the database is configured to not throw exceptions).
Once Again, with Feeling
Connecting to the database, creating the keyspace (and later dropping it), defining the column families and putting in some seed data—I’m probably going to want to do these things a lot within these tests.
That sequence of code is a great candidate to put into pre-test setup and post-test teardown methods.
By dropping the keyspace after and recreating it before each test, I keep the database pristine and in a known state each time I run a test, as shown in Figure 5.
Sweet.
[TestInitialize]
public void Setup()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var keyspace = new CassandraKeyspace(new CassandraKeyspaceSchema {
Name = "Earth",
}, db);
keyspace.TryCreateSelf();
db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
KEY ascii PRIMARY KEY,
FirstName text,
LastName text,
Age int,
Title text);");
var peopleCF = db.GetColumnFamily("People");
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
rickgaribay.FirstName = "Rick";
rickgaribay.LastName = "Garibay";
rickgaribay.HomeTown = "Phoenix";
dynamic theArtistFormerlyKnownAsPrince =
peopleCF.CreateRecord("TAFKAP");
theArtistFormerlyKnownAsPrince.Title = "Rock Star";
db.Attach(tedneward);
db.Attach(rickgaribay);
db.Attach(theArtistFormerlyKnownAsPrince);
db.SaveChanges();
}
}
[TestCleanup]
public void TearDown()
{
var db = new CassandraContext(keyspace: "Earth", server: Server);
if (db.KeyspaceExists("Earth"))
db.DropKeyspace("Earth");
}
‘Look Upon My Works, All Ye Mighty, and Despair!’
Reading data from Cassandra takes a couple of forms.
The first is to fetch the data out of the column family using the Get method on the CassandraColumnFamily object, shown in Figure 6.
Figure 6 Fetching Data with the Get Method
[TestMethod]
public void StoreAndFetchSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic jessicakerr = peopleCF.CreateRecord("JessicaKerr");
jessicakerr.FirstName = "Jessica";
jessicakerr.LastName = "Kerr";
jessicakerr.Gender = "F";
db.Attach(jessicakerr);
db.SaveChanges();
Assert.IsNull(db.LastError);
dynamic result = peopleCF.Get("JessicaKerr").FirstOrDefault();
Assert.AreEqual(jessicakerr.FirstName, result.FirstName);
Assert.AreEqual(jessicakerr.LastName, result.LastName);
Assert.AreEqual(jessicakerr.Gender, result.Gender);
}
}
This is great if I know the key ahead of time, but much of the time, that’s not the case.
In fact, it’s arguable that most of the time, the exact record or records won’t be known.
So, another approach (not shown) is to use the FluentCassandra LINQ integration to write a LINQ-style query.
This isn’t quite as flexible as traditional LINQ, however.
Because the column names aren’t known ahead of time, it’s a lot harder to write LINQ queries to find all the Newards (looking at the LastName name/value pair in the column family) in the database, for example.
Fortunately, CQL rides to the rescue, as shown in Figure 7.
Figure 7 Using Cassandra LINQ Integration to Write a LINQ-Style Query
[TestMethod]
public void StoreAndFetchSomeDataADifferentWay()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic charlotte = peopleCF.CreateRecord("CharlotteNeward");
charlotte.FirstName = "Charlotte";
charlotte.LastName = "Neward";
charlotte.Gender = "F";
charlotte.Title = "Domestic Engineer";
charlotte.RealTitle = "Superwife";
db.Attach(charlotte);
db.SaveChanges();
Assert.IsNull(db.LastError);
var newards =
db.ExecuteQuery("SELECT * FROM People WHERE LastName='Neward'");
Assert.IsTrue(newards.Count() > 0);
foreach (dynamic neward in newards)
{
Assert.AreEqual(neward.LastName, "Neward");
}
}
}
Note, however, that if I run this code as is, it will fail—Cassandra won’t let me use a name/value pair within a column family as a filter criteria unless an index is defined explicitly on it.
Doing so requires another CQL statement:
db.ExecuteNonQuery(@"CREATE INDEX ON People (LastName)");
Usually, I want to set that up at the time the column family is created.
Note as well that because Cassandra is schema-less, the “SELECT *” part of that query is a bit deceptive—it will return all the name/value pairs in the column family, but that doesn’t mean that every record will have every column.
This means, then, that a query with “WHERE Gender=‘F’” will never consider the records that don’t have a “Gender” column in them, which leaves Rick, Ted and “The Artist Formerly Known as Prince” out of consideration.
This is completely different from a relational database management system, where every row in a table must have values for each and every one of the columns (though I often duck that responsibility by storing “NULL” in those columns, which is considered by some to be a cardinal sin).
The full CQL language is too much to describe here, but a full reference is available on the Cassandra Web site at bit.ly/MHcWr6.
Wrapping up, for Now
I’m not quite done with the cursed prophetess just yet—while getting data in and out of Cassandra is the most interesting part to a developer (as that’s what they do all day), multi-node configuration is also a pretty big part of the Cassandra story.
Doing that on a single Windows box (for development purposes; you’ll see how it would be easier to do across multiple servers) is not exactly trivial, which is why I’ll wrap up the discussion on Cassandra by doing that next time.
For now, happy coding!
Ted Neward is an architectural consultant with Neudesic LLC. He has written more than 100 articles and authored or coauthored a dozen books, including “Professional F# 2.0” (Wrox, 2010). He is an F# MVP and noted Java expert, and speaks at both Java and .NET conferences around the world. He consults and mentors regularly—reach him at ted@tedneward.com or Ted.Neward@neudesic.com if you’re interested in having him come work with your team. He blogs at blogs.tedneward.com and can be followed on Twitter at Twitter.com/tedneward.
Thanks to the following technical expert for reviewing this article: Kelly Sommers
|
Le programmeur au travail
Base de données NoSQL Cassandra, 2ème partie : Programmation
Ted Neward
Dans mon article d'août 2012, "Cassandra NoSQL Database : Pour commencer,"J'ai examiné Apache Cassandra.
Il est décrit comme le « open source, distribuée, décentralisée, élastiquement évolutive, hautement disponible, fault-tolerant tuneably cohérente, base de données orientée colonne qui fonde sa conception de la distribution sur Amazon Dynamo et son modèle de données sur Google Bigtable "dans le livre," Cassandra : Le Guide définitif » (o ' Reilly Media, 2010).
Pour être plus précis, j'ai regardé comment installer Cassandre (qui, parce que c'est une base de données Java, aussi nécessaire une Machine virtuelle Java de se lever et s'exécutant sur votre ordinateur si vous n'avez pas déjà), comment s'y connecter depuis la ligne de commande et ce que son modèle de données ressemblé.
Le modèle de données porte répéter parce que c'est assez sensiblement différent dans la structure de la base de données relationnelle qui connaissent bien la plupart des développeurs.
Comme j'ai expliqué la dernière fois (msdn.microsoft.com/magazine/JJ553519), Cassandra est un magasin de données "orienté sur la colonne", ce qui signifie qu'au lieu de stocker de manière identique structuré des tuples de données organisées selon une structure fixe (le schéma de la table), Cassandra stocke des "familles de colonne" dans "keyspaces". En termes plus descriptives, Cassandra associe une valeur de clé avec un nombre variable de paires nom/valeur (colonnes) qui peut être totalement différente d'une « ligne » à l'autre.
Par exemple, considérez l'espace de clé « Terre », j'ai créé la dernière fois, avec une colonne famille nommée « People », dans lequel je vais écrire les lignes qui (peut ou non) ressembler à ceci :
RowKey: tedneward
ColumnName:"FirstName", ColumnValue:"Ted"
ColumnName:"LastName", ColumnValue:"Neward"
ColumnName:"Age", ColumnValue:41
ColumnName:"Title", ColumnValue:"Architect"
RowKey: rickgaribay
ColumnName:"FirstName", ColumnValue:"Rick"
ColumnName:"LastName", ColumnValue:"Garibay"
RowKey: theartistformerlyknownasprince
ColumnName:"Identifier", ColumnValue: <image>
ColumnName:"Title", ColumnValue:"Rock Star"
Comme vous pouvez le voir, chaque « ligne » contient des données similaires sur le plan conceptuel, mais pas tous les « lignes » ont les mêmes données, selon ce que le développeur ou l'entreprise devait stocker pour n'importe quelle touche de ligne particulière.
Je ne sais pas l'âge de Rick, donc je ne pouvais pas stocker.
Dans une base de données relationnelle, si le schéma obligatoire que l'âge était une colonne non NULLABLE, je ne pouvais pas avoir stocké Rick du tout.
Cassandra dit: « Pourquoi pas? »
Mon article précédent démontre insertion et suppression de données de la ligne de commande, mais ce n'est pas particulièrement utile si le but est d'écrire des applications qui vont accéder et stocker des données.
Donc, sans précisions, plongeons-nous dans ce qu'il faut pour écrire des applications qui lire et stockent à Cassandra.
Cassandra, O Cassandre, c'est pourquoi es-tu Cassandra ?
Pour commencer, j'ai besoin de vous connecter à Cassandra de Microsoft .NET Framework.
Cela implique une des deux techniques : Je peux utiliser l'Apache Thrift API native, ou je peux utiliser un wrapper de tierce partie sur le dessus de l'API native d'aubaines.
Aubaines est un binaire remote procedure call toolkit, semblable à bien des égards à DCOM (parie que vous n'avez pas pensé à ça dans quelques années) ou CORBA ou .NET Remoting.
C'est une approche particulièrement bas niveau pour communiquer avec Cassandra, et tandis que l'épargne a c# prend en charge, il n'est pas trivial pour obtenir tout cela vers le haut et en cours d'exécution.
Alternatives à Thrift incluent FluentCassandra, cassandra-sharp, Cassandraemon et Aquiles (la traduction en espagnol d'Achille, qui maintient le thème grec antique vivant et très bien).
Toutes ces sont open source et offrent quelques abstractions plus agréable sur l'API de Cassandra.
Pour cet article, je vais utiliser FluentCassandra, mais aucun d'entre eux semblent fonctionner assez bien, la guerre du feu étrange Internet nonobstant.
FluentCassandra est disponible comme paquet NuGet, donc la meilleure façon de commencer est de tirer vers le haut le gestionnaire de Package NuGet dans un projet de Test Visual Studio (donc je peux écrire des tests d'exploration) et faire un « Package d'installation FluentCassandra. » (La dernière version en date de cette écriture est 1.1.0.) Une fois cela fait, et j'ai vérifié que le serveur de Cassandra est toujours en cours après que j'ai joué avec elle pour la colonne d'août, je peux écrire le premier test d'exploration : connexion au serveur.
FluentCassandra vit dans l'espace de noms « FluentCassandra » et deux espaces de noms imbriqués (« Connexions » et « Tape »), donc je vais traduire les en et puis écrire un test pour voir sur la connexion à la base de données :
private static readonly Server Server =
new Server("localhost");
TestMethod]
public void CanIConnectToCassandra()
{
using (var db = new CassandraContext(keyspace: "system",
server:Server))
{
var version = db.DescribeVersion();
Assert.IsNotNull(version);
testContextInstance.WriteLine("Version = {0}", version);
Assert.AreEqual("19.30.0", version);
}
}
Note qu'au moment où vous lisez ces lignes, il est possible que le numéro de version sera différent de quand je l'ai écrit, donc si cette deuxième assertion échoue, vérifiez la fenêtre de sortie pour afficher la chaîne retournée.
(N'oubliez pas, les tests d'exploration sont sur les tests de votre compréhension de l'API, sortie d'écriture aussi bien d'une mauvaise idée car c'est lors d'un test unitaire automatisé n'est donc.)
La classe CassandraContext a cinq différentes surcharges pour se connecter à un serveur exécutant Cassandra, chacun d'eux assez facile d'inférer — ils sont tous traitent les informations de connexion d'une forme ou une autre.
Dans ce cas particulier, parce que je n'ai pas créé de l'espace dans lequel je veux store (et plus tard lire) des données, je suis de connexion à l'espace de clé « système », qui est utilisée pour stocker divers détails systémiques à peu près la même façon que les bases de données relationnelles les plus ont une instance réservée pour la sécurité et les métadonnées de la base de données par Cassandra et tel.
Mais cela signifie que je ne veux pas écrire dans cet espace de clé système ; Je veux créer mon propre, qui constitue le prochain test d'exploration, comme illustré à la Figure 1.
Figure 1 création d'un espace de clé système
[TestMethod]
public void DoesMyKeyspaceExistAndCreateItIfItDoesnt()
{
using (var db = new CassandraContext(keyspace: "system",
server:Server))
{
bool foundEarth = false;
foreach (CassandraKeyspace keyspace in db.DescribeKeyspaces())
{
Apache.Cassandra.KsDef def = keyspace.GetDescription();
if (def.Name == "Earth")
foundEarth = true;
}
if (!foundEarth)
{
var keyspace = new CassandraKeyspace(new
CassandraKeyspaceSchema
{
Name = "Earth"
}, db);
keyspace.TryCreateSelf();
}
Assert.IsTrue(db.KeyspaceExists("Earth"));
}
}
Certes, la boucle à travers tous les keyspaces dans la base de données n'est pas nécessaire, je le fais ici pour démontrer qu'il existe des endroits dans l'API de FluentCassandra où les pics API basée sur les aubaines sous-jacente à travers et le « Apache.Cassandra.KsDef » type est de ceux-là.
Maintenant que j'ai un espace de clé, j'ai besoin de famille au moins une colonne au sein de cet espace.
La meilleure façon de créer cette utilise Cassandra Query Language (CQL), un langage ressemblant vaguement à SQL, comme illustré à la Figure 2.
Figure 2 création d'une famille de colonne à l'aide du langage de requête de Cassandra
[TestMethod]
public void CreateAColumnFamily()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
CassandraColumnFamily cf = db.GetColumnFamily("People");
if (cf == null)
{
db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
KEY ascii PRIMARY KEY,
FirstName text,
LastName text,
Age int,
Title text
);");
}
cf = db.GetColumnFamily("People");
Assert.IsNotNull(cf);
}
}
Le danger de CQL est que sa grammaire SQL comme délibérément combine avec la simple perception erronée que « Cassandra a colonnes, donc il doit avoir des tableaux comme une base de données relationnelle » de tromper le développeur imprudent en pensant en termes relationnels.
Cela conduit à des hypothèses conceptuelles qui sont sauvagement mal.
Prenons, par exemple, les colonnes de Figure 2.
Dans une base de données relationnelle, seules les cinq colonnes seraient autorisées dans cette famille de colonne.
Cassandra, ceux qui sont justes « lignes directrices » (dans un curieusement « Pirates des Caraïbes » sorte de passage).
Cependant, l'alternative (à ne pas utiliser CQL du tout) est de loin moins attrayant : Cassandra offre l'API TryCreateColumnFamily (non illustré), mais peu importe combien de fois j'ai essayer d'envelopper la tête autour de lui, il se sent encore plus maladroit et déroutante que l'approche CQL.
« Données, données, données !
Je ne peux pas faire de briques sans argile ! »
Une fois la famille de la colonne en place, la véritable puissance de l'API FluentCassandra émerge comme je stocker des objets dans la base de données, comme indiqué dans Figure 3.
Figure 3 stocker des objets dans la base de données
[TestMethod]
public void StoreSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
db.Attach(tedneward);
db.SaveChanges();
Assert.IsNull(db.LastError);
}
}
Notez l'utilisation des installations de c# 4.0 « dynamiques » pour renforcer l'idée que la famille de colonne n'est pas une collection strictement typée de paires nom/valeur.
Cela permet au code c# afin de refléter la nature de la Banque de données orientée colonne.
Je peux voir ceci quand je stocker plus de quelques personnes en l'espace de clé, comme indiqué dans Figure 4.
Figure 4 stocker plus de personnes dans l'espace de clé
[TestMethod]
public void StoreSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
rickgaribay.FirstName = "Rick";
rickgaribay.LastName = "Garibay";
rickgaribay.HomeTown = "Phoenix";
dynamic theArtistFormerlyKnownAsPrince =
peopleCF.CreateRecord("TAFKAP");
theArtistFormerlyKnownAsPrince.Title = "Rock Star";
db.Attach(tedneward);
db.Attach(rickgaribay);
db.Attach(theArtistFormerlyKnownAsPrince);
db.SaveChanges();
Assert.IsNull(db.LastError);
}
}
Encore une fois, juste pour enfoncer le clou, remarquez comment Rick a une colonne ville natale, qui n'a pas été spécifiée dans la description précédente de cette famille de colonne.
C'est complètement acceptable et assez fréquent.
Notez également que l'API de FluentCassandra offre la propriété « LastError », qui contient une référence à la dernière exception levée hors de la base de données.
Cela peut être utile de vérifier quand l'état de la base de données n'est pas déjà connu (tels que lors du retour d'un ensemble d'appels et qui croyait avoir mangé l'exception levée, ou si la base de données est configurée pour ne pas lever d'exceptions).
Une fois de plus, avec le sentiment
Connexion à la base de données, créer l'espace de clé (et plus tard en le déposant), définissant la famille de la colonne et mettre certaines données semences — je vais probablement vouloir faire ces choses beaucoup au sein de ces tests.
Cette séquence de code est un candidat idéal pour mettre en configuration pré-test et post-test méthodes de démontage.
En supprimant les clés après et recréer avant chaque essai, je garde la base de données vierge et dans un état connu chaque fois que je lance un test, comme indiqué dans Figure 5.
Sweet.
Figure 5 exécution d'un Test
[TestInitialize]
public void Setup()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var keyspace = new CassandraKeyspace(new CassandraKeyspaceSchema {
Name = "Earth",
}, db);
keyspace.TryCreateSelf();
db.ExecuteNonQuery(@"CREATE COLUMNFAMILY People (
KEY ascii PRIMARY KEY,
FirstName text,
LastName text,
Age int,
Title text);");
var peopleCF = db.GetColumnFamily("People");
dynamic tedneward = peopleCF.CreateRecord("TedNeward");
tedneward.FirstName = "Ted";
tedneward.LastName = "Neward";
tedneward.Age = 41;
tedneward.Title = "Architect";
dynamic rickgaribay = peopleCF.CreateRecord("RickGaribay");
rickgaribay.FirstName = "Rick";
rickgaribay.LastName = "Garibay";
rickgaribay.HomeTown = "Phoenix";
dynamic theArtistFormerlyKnownAsPrince =
peopleCF.CreateRecord("TAFKAP");
theArtistFormerlyKnownAsPrince.Title = "Rock Star";
db.Attach(tedneward);
db.Attach(rickgaribay);
db.Attach(theArtistFormerlyKnownAsPrince);
db.SaveChanges();
}
}
[TestCleanup]
public void TearDown()
{
var db = new CassandraContext(keyspace: "Earth", server: Server);
if (db.KeyspaceExists("Earth"))
db.DropKeyspace("Earth");
}
« Regard sur mes oeuvres, vous tous puissant et le désespoir! »
Lecture des données de Cassandra prend deux formes.
La première consiste à extraire les données hors de la famille de colonne à l'aide de la méthode Get sur l'objet de CassandraColumnFamily, dans Figure 6.
La figure 6, extraction de données avec la méthode Get
[TestMethod]
public void StoreAndFetchSomeData()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic jessicakerr = peopleCF.CreateRecord("JessicaKerr");
jessicakerr.FirstName = "Jessica";
jessicakerr.LastName = "Kerr";
jessicakerr.Gender = "F";
db.Attach(jessicakerr);
db.SaveChanges();
Assert.IsNull(db.LastError);
dynamic result = peopleCF.Get("JessicaKerr").FirstOrDefault();
Assert.AreEqual(jessicakerr.FirstName, result.FirstName);
Assert.AreEqual(jessicakerr.LastName, result.LastName);
Assert.AreEqual(jessicakerr.Gender, result.Gender);
}
}
C'est super si je sais que la clé avance sur le temps, mais la plupart du temps, ce n'est pas le cas.
En fait, on peut soutenir que la plupart du temps, l'ou les enregistrements exacts ne s'appeler.
Alors, une autre approche (non illustrée) consiste à utiliser l'intégration FluentCassandra LINQ pour écrire une requête de style LINQ.
Ce n'est pas tout à fait aussi souple que LINQ traditionnel, cependant.
Parce que les noms de colonnes ne sont pas connus en avant du temps, il est beaucoup plus difficile d'écrire des requêtes LINQ pour rechercher toutes les Newards (regardant la paire nom/valeur LastName dans la famille colonne) dans la base de données, par exemple.
Heureusement, CQL rides à la rescousse, comme le montre Figure 7.
La figure 7 à l'aide de Cassandra intégration de LINQ pour écrire une requête LINQ-Style
[TestMethod]
public void StoreAndFetchSomeDataADifferentWay()
{
using (var db = new CassandraContext(keyspace: "Earth",
server: Server))
{
var peopleCF = db.GetColumnFamily("People");
Assert.IsNotNull(peopleCF);
Assert.IsNull(db.LastError);
dynamic charlotte = peopleCF.CreateRecord("CharlotteNeward");
charlotte.FirstName = "Charlotte";
charlotte.LastName = "Neward";
charlotte.Gender = "F";
charlotte.Title = "Domestic Engineer";
charlotte.RealTitle = "Superwife";
db.Attach(charlotte);
db.SaveChanges();
Assert.IsNull(db.LastError);
var newards =
db.ExecuteQuery("SELECT * FROM People WHERE LastName='Neward'");
Assert.IsTrue(newards.Count() > 0);
foreach (dynamic neward in newards)
{
Assert.AreEqual(neward.LastName, "Neward");
}
}
}
Notez, cependant, que si je lance ce code tel quel, il échouera, Cassandra ne me laisse pas utiliser une paire nom/valeur au sein d'une famille de colonne comme critère de filtre, sauf si un index est défini explicitement à ce sujet.
Cela nécessite une autre instruction CQL :
db.ExecuteNonQuery(@"CREATE INDEX ON People (LastName)");
En général, je tiens à mettre que vers le haut au moment où la famille colonne est créée.
Notez également que Cassandra étant sans schéma, le "sélectionnez *" partie de cette requête est un peu trompeur — elle renverra toutes les paires nom/valeur dans la famille de la colonne, mais cela ne signifie pas que chaque enregistrement possède toutes les colonnes.
Cela signifie donc qu'une requête avec "où sexe = « F »" ne considère jamais les enregistrements qui n'ont pas une colonne « Genre » en eux, qui laisse Rick, Ted et « L'artiste anciennement connu comme Prince » d'une considération.
C'est complètement différent d'un système de gestion de base de données relationnelle, où chaque ligne d'un tableau doit avoir des valeurs pour chacun des colonnes (même si j'ai souvent canard cette responsabilité en stockant la valeur « NULL » dans ces colonnes, qui est considéré par certains comme un péché capital).
La langue CQL complet est trop difficile à décrire ici, mais une référence complète peut être consultée sur le site Web de Cassandra à bit.ly/MHcWr6.
La conclusion, pour l'instant
Je n'ai pas tout à fait fini avec la prophétesse maudite tout de suite, tandis que l'extraction de données dans et hors de Cassandre est la partie la plus intéressante pour un développeur (car c'est ce qu'ils font toute la journée), une configuration à plusieurs nœuds est aussi une assez grande partie de l'histoire de Cassandra.
Faire cela sur une seule boîte de Windows (à des fins de développement ; vous verrez comment il serait plus facile à faire sur plusieurs serveurs) n'est pas exactement trivial, c'est pourquoi je vais terminer la discussion sur Cassandra en faisant que la prochaine fois.
Pour l'instant, amusez-vous !
Ted Neward est consultant en architecture chez Neudesic LLC. Auteur de plus de 100 articles, il a rédigé ou corédigé plus d'une dizaine d'ouvrages, y compris « Professional F# 2.0 » (Wrox, 2010). Il est un éminent expert Java MVP F # et prononce des conférences fois Java et .NET dans le monde entier. Il consulte et mentors régulièrement — le joindre à ted@tedneward.com ou Ted.Neward@neudesic.com si vous êtes intéressé par lui avoir à venir travailler avec votre équipe. Il blogs à blogs.tedneward.com et peut être suivi sur Twitter à Twitter.com/tedneward.
Merci à l'expert technique suivant d'avoir relu cet article : Kelly Sommers
|
|
Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.
|