Wednesday, 4 November 2015

Twitter - We should talk.

Hi Twitter.

Please, come in and sit down. I think it's time we had a little chat.

I know things haven't been great lately, and I think you're coming undone at the seams a bit.

I'd like to lay a few things out for discussion.


Your position.


You are in a unique position of being a vital, real-time connection between content producers and interested parties, who may also be content producers. You enjoy a position which is (blessedly) outside Facebook and is not a walled garden. You have made amazing technical advances to be able to support this amazing real-time platform. You are, in general, viewed as a force for good. You have friends.

I have witnessed more news, events, conversations and opinions on Twitter than almost any other medium I've used in the past twenty years that I've been on the Internet. It's addictive to be part of this huge, global conversation and I love that.

Recent activity.

But you keep doing these things.

Layoffs.

Maybe these were justified because you had over-hired, but it looked reactionary which, if I was one of your investors, would make me quite nervous and/or irritated. Stop jerking your knees. Be more solid.

Moments.

Experimental. I think it's good to separate curated items like this, which leaves timelines alone. More on that later. Definitely interesting as a thing for surrounding current affairs and getting people interested in them. The "You're caught up" is very Circa-like (RIP Circa). The Trending items have been kind of filling that purpose already, though, but the presentation in Moments is very clean, so I hope that this can build on that kind of attention into something cool. But I'm really not sure that this is the new user grabber that you think it is.

Please don't retire trending or hashtags though. That would be uncool.

Polling.

The polling functionality is a really powerful thing. You can do a lot of good in this area. Keep up with the good work on this. Be a part of online democracy and live reaction. We like.

Periscope.

Still niche, but an excellent reporting tool. It would be nice to see better integration of that in things like the TweetDeck Chrome extension. Real problems here are bandwidth, video quality, people's data plans, vertical aspect video.

Vine.

I know Instagram do 15 seconds, but 6 seconds is still cool, right? The integration of this service makes sense because it lends itself to the concise nature of a Tweet. However, the only people I have really seen (in the UK) using Vine are comedians. Think about that for one of those 6 seconds.

Apparent lack of action regarding the tackling of online abuse.

You really, REALLY need to sort this shit out. Provide tools, streamline reporting processes, support victims, unleash rogue AI... Literally anything apart from sitting on your hands about bullying and threatening behaviour.

Favourites vs likes.

Stars vs hearts. Connotations and implications. Organisation vs emotion. I could go on. In fact ...

The whole point of Web 2.0 was the ability to customise your web experience. What happened? How can it not be clear that this change makes it means something totally different? It smacks of flag-waving. You must stop doing these kinds of changes. People (read: your user base) get very used to these semantics and symbols, and you'd be amazed what could drive a person away if they felt aggrieved enough about it. As it is, it's now fairly evident that the people who make these kinds of decisions don't appear to use Twitter or "get it" - the blog post from Akarshan Kumar made it clear that you are going for lowest common denominator, and that's extremely patronising.

The best possible thing you can do here is to bring back the Star icon, and call the collection of things "Starred", because that is completely agnostic to emotional connections. Then this feature can go back to its meaning being implied from context (which includes timing) and also have zero connotations when used as an organisational method. Please do this because what you've done makes no sense to me and many others.

Noise.

Most of Twitter is noisy. If I step outside of the accounts I follow then I find myself in a horrific sea of chatter. I'm actually cool with that. It supports the notion of 'tuning-in' to certain voices; apart from the Etsy bots - I'm f-ing sick of those. You should be able to spot them and many other types of spammer by now - so why aren't they automatically banned?

Concentration on in-house clients.

At the cost of pissing off every other development team who ever tried to make a 3rd party add on or client for Twitter. It's like you really don't want an ecosystem at all. You are a PLATFORM, a SERVICE, let people MAKE THINGS THAT USE YOUR PLATFORM. If this is about the whole advertising thing - i.e. that you can't control placement of adverts in 3rd party clients, then you're doing things backwards. FIND ANOTHER MODEL (see below). It's not like you're showing adverts in TweetDeck's chrome extension yet anyway (and please don't start now - I'll cry).

Perverse notion of messing with the chronology of timelines.

Twitter's utility is that it shows a chronological view of content. I have absolutely no idea why you think that modifying this is in any way a good thing to do. Even the the out-of-order replies reaction (the "blue line" fiasco) was a UX-disaster with people (like me) complaining about the cognitive dissonance it provoked and being the final nail in the coffin for staying the hell away from the web client. The considered plans for "Tweets-you-may-have-missed" is just barmy. If there are things that you think I've missed - PUT THEM INTO A SEPARATE LIST. Seriously, if you cock up the chronology of Tweets in a timeline then you might as well be a randomly ordered collection of things people have said at some point in the past X hours like Facebook. Just stop that line of thinking.

Consideration of Tweets longer than 140 characters.

No, no, no, no, NO. Twitter is concise. Keep it that way. How am I supposed to catch up on 500+ Tweets that happened overnight if they are a bunch of essays? That's what I have RSS for! If someone wants to post more than 140 characters then they should host content elsewhere which has meta-data on it that allows the previews in clients - just like they do now. CREATE AN ECOSYSTEM, NOT A WALLED GARDEN.

Also - please support RSS. RSS is your friend.

The web client.

I mean, seriously. I'm really glad that we got the Bootstrap project out of Twitter (I use it every day at work), but my God you guys really need to completely re-think your UX. Jesus F-ing Christ, what a mess. If I was approaching that project I would absolutely throw the entire template away and hope that no-one ever mentioned it at parties I went to.

Stock price.

By doubling down on bad ideas to please investors, all it goes to show is that you have collected the wrong investors, with the wrong promises whispered into the wrong ears.

The knock-on effects of promising swathes of new users is removing focus from what makes your platform great.

New users are only valuable if you can present advertising to them. And you've seen the current ad-block fuss.

What you need are customers.

Recommended actions.


Subscription model.


I would absolutely pay $5-10 dollars a month for a subscription to Twitter. That subscription would be ad-free, with a reasonable expectation of timely support for problems with my account, payment, online abuse, and the knowledge that my 3rd party Twitter client could be used without limitation.

I can envisage you running a subscription programme in parallel with your current operations, the only difference being the ad-free nature of the channel, a better SLA for support and the unlocked API access.

The more I think about it, (and I've been thinking about this for a few years), the more strongly I believe that this would be hugely successful for you.

But how much would subscription actually cost you? What change would that have on revenue when a (hopefully significant) percentage of your users are throwing you money each month? What would be the effect upon new user take-up, if they joined knowing that they could upgrade their experience? What would be the effect on API use if it was suddenly opened up to paying users?

I can't help but feel that these changes would be positive.

Content producer payment.

Given that many people join up to be able to follow people with something to say, celebrities, authors, musicians, news outlets, etc, then perhaps that should be rewarded somehow.

There are a few different services which allow users to make payment via Twitter. By taking that in-house this could be like your own version of Patreon, (minus the data leakage), offering content producers payback for being popular.

Again, this is about creating an ecosystem. Reward people for taking part.

Signing off.

Twitter, I don't know if you'll read this. I hope you do and I hope that some of it gets through to you. You are a vital, current thing, and it is in everyone's interest (apart from Facebook's) that you stop these self-harming actions and become something better.

Good day to you, whomever you are.

@fractos.

Monday, 21 September 2015

Fractos' axiom

Fractos' axiom:

There are only three types of software bug:


  1. Namespace issues
  2. Sequence mistakes
  3. Typos
  4. Out by one errors

Thursday, 30 July 2015

Books, mostly

Hola.

My last set of posts about Using the Model-Store pattern in Inversion took a large heap of my blogging-concentration, but as they've been going since March it's good that they are finally out of the way.

Work's going well and I'm getting my teeth stuck into producing a cloud-based platform for digital library services for digitised collections, services like deep-zoom image serving, discovery, managing metadata and work with the emerging IIIF standard for viewers. Work days, I'm at Wellcome, which is a bit like being on a spaceship, although the food is probably better than NASA serve.

I'm cycling to work which is moderately terrifying. Drivers are terrifying. Cyclists are terrifying. Especially cyclists, frankly, considering some of the whacked-out shit I've seen them do in just a few weeks of riding.

At the moment I'm studying for the Microsoft Programming in C# Exam (70-483) which is on Monday afternoon. It helps out Digirati and definitely doesn't hurt me to go for the certification. However, the study has kinda precluded me making much progress with the stuff I'm reading.

Oh, I bought a Microsoft Band the other day, partly because geek and partly to mitigate an RSI I'm getting in my right index finger. Reaching for my phone in jeans pockets every 30 seconds is really causing a problem, it seems, so the Band picks up the notifications for me. The cycle / run tracking is really good too, although I was tipped over into buying one when I learned that they'd opened up the SDK to allow arbitrary RSS feeds to be added to the tiles.



Go get one! They're really cool.

Currently reading:

Assassin's Apprentice (Farseer Trilogy part 1)
Robin Hobb




Building Microservices
Sam Newmann


(Well, I lie - I just finished it the other day - really bloody good, though.)

And my exam book:
Programming in C# Exam Ref 70-483
Wouter de Kort


Which has got a whole bunch of text inconsistencies (calling things modules, for example), some really bad practice (if you generally mark your classes as sealed then you, whomever you are, are a sociopath), and some answers missing. So that's nice.


Tuesday, 21 July 2015

Using the Model-Store pattern in Inversion (part 5)

This post is the fifth in a series about using a Model-Store pattern in the Inversion behavioural micro-framework for .Net (see: https://github.com/guy-murphy/inversion-dev).
The first part can be found here.
The second part can be found here.
The third part can be found here.
The fourth part can be found here.

Aftermath


Looking back, I might as well have titled this essay - "I hate ORMs".

Firstly, because I really, really do - I think they are at the root of many evils in .Net software architecture. That's not to say that its the ORMs fault, because it really isn't. However, time after time we are told to "just use" Entity Framework or N-Hibernate because that's what Microsoft or the big boys have blogged about, and time after time I see people getting into the same mess with a half-cocked Repository pattern, absurdly heavyweight models and bloody DTOs springing up all over the place. What I want is a clean, quick, safe and lightweight pattern of data access, and an ORM has never convinced me that it is any of these things because people tend to abuse the functionality it provides without thinking about the consequences or that there might be other ways to approach data access.

Secondly, I didn't actually need to say that this was using Inversion, because Model-Store can be done in any .Net framework. However, it's been useful to write the foundations of the Inversion.Data namespace by considering the paths that writing this article has led me down.

The views in this set of articles almost feel heretical when looking at the current "best practice" of .Net application development. Having an immutable model POCO and a lightweight store (which eschews ORM-like functionality) shifts complexity away from the data access implementation, forcing any business logic back to the application where (in my opinion) it belongs. Data access should be dumb, MUST be dumb, if the application is to be agnostic about the storage technology to be used. This massively increases the platform and language options available for porting and the development of new features. I would counsel avoiding relying upon the features of a particular storage medium, because you never know when that medium could become suddenly unsuitable or unavailable.

Model objects are best designed very closely to the underlying table or document structures. This is for the simplicity of maintenance and the ease of introduction to new development staff. A Model should remain at a level of abstraction such that the properties would be the same across common types of database representation. Therefore, an application that uses this pattern can achieve granular control over the strategies used for reading, writing and updating individual data classes whilst also remaining agnostic to storage technology choices.

When developing the MyOffers.co.uk data analytics engine, we tended to write Stores for at least two storage mediums, e.g. MS Sql Server and Redis and/or MongoDB. This forced us into keeping data access straightforward while letting us concentrate on what the application actually needed to accomplish. These same patterns helped us again when writing a cloud-based quiz engine that would operate at scale and the new version of the website application. It forms part of a discipline that assists what I believe to be a fast and clean way of considering modern system architecture.

Immutable Models and CQRS/Event-Sourcing


There are other benefits that immutable Models can bring. If you take the basic tenet that a Model is immutable, then it follows that any change that is required to a Model can be represented as a separate thing. This is the basis of an architecture which gives CQRS-like benefits to an application that uses immutable Models in combination with read-only, optimised Model Stores, with changes persisted in the form of Events to write-capable Stores which could have a completely different storage medium that is optimised for write, thus separating the reads (a Query) from the writes (a Command).

This helps support eventual consistency, because any changes must be persisted explicitly and the source that the read-optimised Store fetches from can be lazily updated by a mechanism that is in the background. With this architecture, concurrent threads are guaranteed to read the same value when referencing an object, and modifications can be made without having to worry about locking or making a local copy.

Ending


It is not necessary to use the whole of Model-Store to get these advantages. However, the Store part of the pattern provides an efficient way of packaging data access which can use the immutable Model objects, and shows us that when implementing a DAL in the .Net ecosystem there are other options than Repository patterns and ORMs like Entity Framework or N-Hibernate.

If you haven't agreed with me so far, then perhaps the most compelling thing I can offer is that, most of the time, an application need only read what the values of a Model object are; updates and writes are scarce by comparison. Why weigh your application down with the ability to track changes in an object when you don't use it in most operations?

If you design your application to make changes explicitly then you can take advantage of the benefits of lightweight data access.

I've wanted to document some of my thoughts about this pattern for a while, and I do hope that some of you have stuck through to the end. There are many things that can be pointed to in this set of arguments which other developers may not agree with, and that's fine. As I said, this style of pattern may not be for you. However - I have seen it work in small, medium and production-scale systems, making them fly, whilst also providing a great deal of freedom as a developer.

At the very least - please check out the Inversion framework. Full disclosure being that I'm a major contributor for it and I am using it very happily in the production of a large, automatically scaling cloud-based system for a major institution at the moment, including the bits and pieces you will find in the Inversion.Data namespace.

Dream large.

Sunday, 12 July 2015

Using the Model-Store pattern in Inversion (part 4)

This post is the fourth in a series about using a Model-Store pattern in the Inversion behavioural micro-framework for .Net (see: https://github.com/guy-murphy/inversion-dev).

The code that I have been developing as part of this article series can be found at https://github.com/fractos/inversion-data

The first part can be found here.
The second part can be found here.
The third part can be found here.

Using the Store


In this instalment, I'm going to show you what using the pattern looks like. I'll put together a few example functions that use the IUserStore interface to access and manipulate data stored in an underlying MongoDB instance.


Configuring and Instantiation



We are going to use the MongoDBUserStore class that I showed you in the previous article to perform operations in MongoDB. To instantiate it, we will need a few details that describe the MongoDB connection-string, the database name and the name of the collection used for users.

I am using a local MongoDB server, which has a database called 'test' and I want to access model objects in a collection called 'users':



Those details can then be passed to the MongoDBUserStore constructor:



But since this is an IDisposable and we only actually care about the interface methods, then more correctly we should be calling it like this:



At which point we are off to the races.

However, the Store could be gained by dependency injection behind an interface, or fetched from a service container (such as Inversion.Naiad, which implements Inversion.Process.IServiceContainer). I'll try and cover the use of IServiceContainer in a separate article, but the call might look something like this:



By using interfaces alone, you can keep your code free from references to the assemblies needed for accessing MongoDB, keeping the main application as agnostic as possible. All the code cares about is the IUserStore interface.

Asking a service container for an object of type IUserStore which has the label "store-user" will cause the container to perform any configuration required according to the instructions that are associated with the service label. The application doesn't have to care about the configuration of the Store because it is handled by the container. This is a form of loose coupling by service location.

The more agnostic your code is about its dependencies, the more flexible your application can be.

Creating a collection of Users in MongoDB


This method assumes that you have a list of User models which are ready to be written to the database. I've included the basic version of instantiating the Store just to keep it simple.



After this call, you should find that the User models have been written into MongoDB, complete with Object IDs.

Read a list of Users from MongoDB


This method will return all the users from MongoDB as a list of User models.

Note that the IEnumerable is converted to an IList with ToList() before the using statement has been terminated (and therefore the Store and its resources disposed) in order to avoid the enumeration code reading a closed connection.



Updating a list of Users


This function iterates through a passed list of User models, modifying their Password property and then writing them back into MongoDB. It makes use of the Mutate method on the User object in order to change an immutable object. The ID of the User model will remain unchanged, however, so the act of writing the data into MongoDB will perform an update rather than an insert.




Deleting a list of Users


This function will delete each of a list of User models from MongoDB.



Next time ...


In the next (and final, so far ...) instalment, I'll wrap up with a conclusion and some thoughts about how this process went.

Thursday, 14 May 2015

Reading progress ...

Finished William Gibson's "The Peripheral" earlier this week after reaching about 80% of the way through it, realising that I was totally absorbed in it and that I didn't want it to end. So, I restarted the book and I am so glad that I did. It's an amazing, lushly detailed work. Very happy that he has returned to sci-fi with what might possibly be his finest work.



There are so many books queued up on my Kindle. I've recently got back into going to the gym where I tend to get a good hour's worth of reading done if I find a free cross-trainer. Now I can start to chew through the pile of purchases I've made over the past six months since I moved into my fiancé's place from my old flat where the gym was just over the road.

First up, I'm hitting the start of the Farseer Trilogy by Robin Hobb. The whole series were on offer a while back and I was a bit click-happy.

Currently reading:

"Assassin's Apprentice (Book One of the Farseer Trilogy)" by Robin Hobb



Saturday, 9 May 2015

Using the Model-Store pattern in Inversion (part 3)

This post is the third in a series about using a Model-Store pattern in the Inversion behavioural micro-framework for .Net (see: https://github.com/guy-murphy/inversion-dev).

The first part can be found here.
The second part can be found here.

Introduction


I finished the last post by describing the base Store for dealing with a MongoDB database. Now I will go through the specialised Store that uses it to perform operations with the User class I introduced in part 1.

Reminder - you can find the source code for this article at https://github.com/fractos/inversion-data.

Forced specialisation of Models


The first thing I need to cover is that I had to rewrite a whole bunch of stuff in this article after trying really hard to make the examples work with the original Model. This is because of the annoying behaviour in MongoDB of it requiring a property which it deserialises the database object ID into. It's not the only database that enforces this kind of thing (I'm looking at you, DynamoDB), and it's also an unwelcome reminder that sometimes we really are at the mercy of the storage gods.

I've also tried hard to make the examples work with simple serialisation and deserialisation to and from the Model's Builder object, but it just doesn't work. The automatic deserialisation forces you to change the original Model and none of my attempts at wrapping the Model in a way that serves as an adaptor seem to work. Maybe there is a way to wrap it, but the extra plumbing just doesn't feel worth the hassle.

As it is, the User class needs an ID, as the operations performed by the specialised Store become over-complicated without one. I've listed the new source of User.cs, below. Note that the ID property is set to a unique value in the User.Builder parameterless constructor, which means that any newly created User object will have a unique ID already assigned.

In order for the ID to be used correctly with the automatic deserialisation in MongoDB's driver, the type of the ID would have to be changed (to ObjectId) and the property itself marked with a Bson-specific attribute (BsonId). However, I'm not willing to do that as it invalidates the whole idea of having a Model class that is agnostic to the storage technology being used.

So I'm going to show you another way.



Another way around - BsonDocuments


As I mentioned in part two, BsonDocuments are objects that represent a piece of BSON data, which is a binary-serialised version of a JSON document peculiar to MongoDB. The driver is very happy dealing with these constructs as they provide a basic representation of the document. This representation is used internally in the database for binary serialisation. I use the BsonDocument method in all my projects that back onto MongoDB.

Properties are accessible by their name, and they can be any one of the primitive types that MongoDB supports which includes strings, dates, numbers, objects (i.e. another document) and arrays.

So, a way of getting around the forced specialisation of a Model type is to provide custom serialisation/deserialisation to and from a BsonDocument as presented and consumed by the MongoDB driver.

To do this, I use extension methods as a tidy solution for compartmentalisation of the conversion methods.

Using extensions to group conversion methods - MongoDBUserEx.cs


This is the static class that sits in the same namespace as the specialised store MongoDBUserStore. Inside are two extension methods - ConvertUserToBson and ConvertBsonToUser. They pretty much do what they say on the tin. Here's the code.



The ConvertUserToBson method extends an instance of the User class and returns a prepared BsonDocument containing a document representation of the User object's data. The document properties are accessed by name, hence the string indexer on doc. The reserved property name "_id" is used to set the document ID which is set from the User object's ID property.

The "username" and "password" document properties are simply set with the respective string values from the User object.

The "metadata" property, however, is set with a different BsonDocument which is made from the contents of the User object's Metadata property. The BsonDocument constructor can take a IDictionary of values and is quite happy to accept the raw Metadata dictionary which is exposed as IDictionary.

The ConvertBsonToUser method extends an instance of the BsonDocument class and returns a populated User object. Again, the document properties are accessed by name and are used to set the values of a User.Builder object. For the ID, the string value of the underlying ObjectId type is used.

The "metadata" property is retrieved as a BsonDocument, and then its Names property is used as the basis of a ToDictionary call which creates the keys and values of the Builder's Metadata object.

The act of returning the User.Builder object implicitly converts it into an immutable User object.

Interface for the User Store - IUserStore.cs


Here is the interface that I will be implementing for the specialised User Store.




The interface is defined as implementing the IStore interface, so certain methods will be expected to be implemented. However, these should be satisfied by the ancestor of our specialised Store - StoreBase.cs.

There is a Get method that accepts a username to search by, returning an immutable User object. Then there is a GetAll method that should return all the items in the database as an IEnumerable of User objects. I reckon the interface could probably do with a Get method that takes a user's ID value as a parameter, but feel free to do that yourselves (one of them will have to be renamed as they both accept a lone string parameter).

Then the Put method, taking an individual User object which will be entered into the database. I'm assuming that Put also serves as an Update operation in this implementation.

Finally there is a Delete operation that accepts a User object as its target to remove from the database.

Specialised Store for our User model - MongoDBUserStore.cs


Now we have the interface and those conversion tools available, we can proceed with the specialised Store for the User model.

I should point out that this Store flagrantly subverts the new, asynchronous capabilities of the MongoDB driver. This is because not all storage technologies will support async/await style calls and one of the main goals of this pattern is to allow any storage technology to be used behind the same interface. Having some implementations use async/await while others do not is therefore a really bad idea because it forms a leaky abstraction - you need to know from outside the component whether it can be called asynchronously. Further, if an async/await call to an IDisposable object is incorrectly executed, then resources could end up being disposed before an asynchronous call has completed. So, I believe that if you are dealing with async/await calls then you should define an explicit interface for them to make it clear to the developer calling the interface that they will be dealing with the side-effects themselves.

    public class MongoDBUserStore : MongoDBStore, IUserStore

The class signature shows that we are inheriting from the basic MongoDBStore class, and we will be exhibiting the behaviour specified in the IUserStore interface.

Constructor and properties


        

The constructor takes the information needed by the base Store - connStr and dbName, and passes them into the base class constructor. It also takes a collectionName argument which is stored in a private, read-only property. It is assumed that this specialised Store for the User object will only be dealing with a single MongoDB Collection.

Start


        

Knowing the name of the collection up front allows us to perform this operation during the Start method, after having called the base implementation to change the Store state. This step retrieves an IMongoCollection object with which we can perform queries and commands upon the named collection.

Get


        

Now we are starting to implement the IUserStore interface with the first method - Get. The first thing to do is to assert whether the Store is in the correct state to perform operations, i.e. whether Start has been called. The AssertIsStarted method is called which you may remember is implemented on the grandparent Store class.

The query itself is performed by calling the Find method of the IMongoCollection we initialised in the Start method. It is passed a BsonDocument that contains the information to evaluate appropriate documents in the collection. In this case, we set the "username" property in the document to the value that we are looking for. Only the first result is needed (or expected), and so the FirstAsync method is called on the result of the Find operation. Because we are wrapping asynchronous behaviour in this implementation, we will wait for the result of the FirstAsync method by accessing it's Result property. Finally, the Result property (which will be of type BsonDocument to match the generic type of the IMongoCollection) has the extension method ConvertBsonToUser called upon it, that will instantiate an immutable User object that contains the values from the document.

That's a lot of behaviour wrapped up in one line. Fluent interfaces like this which libraries like System.Linq allow are fantastic to use (when you know what you are doing), but unwinding the meaning of them by way of explanation can be a time-consuming business.

GetAll


        

The next part of the IUserStore interface to be implemented is GetAll. This simply returns the entire contents of the collection. However, to do that we need to make a query that will return all items, then turn that list of results into an IEnumerable of the correct User objects. More fluency in the calling methods for this gives us those results.

Special note should be taken that the query used is represented by an empty BsonDocument passed to the FindAsync method - no matching criteria means that it matches against all the items in the collection. The result of the Find operation must be transformed into a list and that is performed by the ToListAsync method. Again, we must wait for the result and this gives us a List which then gives us access to the Select method that we can use to call the ConvertBsonToUser method upon each item finally resulting in an IEnumerable that is returned back to the caller.

Put (single User object)


        

We use the MongoDB driver's Replace method to effectively perform an Upsert operation on the database, creating or replacing an existing entry which has the same ID.


Delete


        

And finally the Delete method, giving us all the CRUD operations (provided you don't mind if the Create and Update are represented by the Put method). Again, we use the BsonDocument method of passing the matching criteria into the driver.

Next time ...


In the next part of the series, I'll show you how to use the Store in practice.