Skip to content

Commit

Permalink
Covering tests, why it isn't advisable to use IRepository
Browse files Browse the repository at this point in the history
  • Loading branch information
ayende committed May 19, 2017
1 parent 300f95b commit 4627c30
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 10 deletions.
204 changes: 199 additions & 5 deletions Ch02/Ch02.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@

# Zero to RavenDB
# Zero to RavenDB

[zero-to-ravendb]: The very first steps we need to take in our journey to understand RavenDB is to actually getting
[Introduction]: #zero-to-ravendb


The very first steps we need to take in our journey to understand RavenDB is to actually getting
it running on our machine, so we can actually get things done. I'm deferring discussion on what it is, how it works,
etc to a later part of this book, because I think that having a live version that you can play with will make it
much easier to understand RavenDB.
Expand Down Expand Up @@ -162,7 +165,7 @@ you'll see that instead of just storing a few columns, we can store rich informa
objects (`ShipTo` property) or arrays of complex types (the `Lines` property).

This means that we don't have to split our data to statisfy the phyiscal constraints of our storage, we can store
a whole object graph in a single document. We'll talk about modeling a lot more in [Chapter 3](#crud), but for now
a whole object graph in a single document. We'll talk about modeling a lot more in [Chapter 3](#modeling), but for now
I'll just mention that the basic modeling method in RavenDB is based around Root Aggregates and complete the
picture in the next chapter.

Expand Down Expand Up @@ -285,7 +288,7 @@ configuring it as showing in Listing 2.3.
var store = new DocumentStore
{
Urls = { "http://localhost:8080" },
DefaultDatabase = "Tasks"
Database = "Tasks"
};

store.Initialize();
Expand Down Expand Up @@ -489,7 +492,7 @@ public class DocumentStoreHolder
"https://ravendb-02:8080",
"https://ravendb-03:8080",
},
DefaultDatabase = "Tasks",
Database = "Tasks",
};

documentStore.Initialize();
Expand Down Expand Up @@ -982,6 +985,33 @@ You can make the session forget about an entity (it won't track it, apply change
I'm not going to go over the `Advanced` options here. They are quite a few, and they covered in the documentation quite nicely.
It is worth to take the time to read about it, even if you will rarely have to use that.


> ** Hiding the session:** avoid the `IRepository` mess
>
> A common problem that we see with people using the RavenDB Client API is that they frequently start by defining their own
> Data Access Layer, usually named `IRepository` or some such.
>
> This is generally a bad idea. We have only started to scratch the surface of the RavenDB Client API and you can already see
> that there are features (`Includes`, optimistic concurrency, change tracking) that are very valuable. Hiding behind a
> generic interface typically result in one of two options:
>
> * A generic interface doesn't expose the relevant (and useful) features of RavenDB, so you are stuck with using the lowest
> common denominator. That means that you give up on a low of power and flexibility, and in 99% of the cases, will fail to
> allow you to switch between data stores implementations^[The 1% case where it will help is the realm of demo apps with
> little to no functionality].
>
> * Because of the previous point, you expose the RavenDB features behind the `IRepository`, in which case you are already
> tied to the RavenDB client, but you added another layer that doesns't really add anything, but increase code complexity
> and can make it hard to understand what is actually going on.
>
> The RavenDB Client API is meant to be easy to use and high level enough so you'll not need to wrap it for convienance sake.
> If all you are doing is forwarding calls back and forth, there is little point in doing so.
>
> One thing that is absolutely wrong to do, however, it to have methods like `T IRepository.Get<T>(string id)` that will
> create and dispose of a session within the scope of the `Get` method call. That loses out of a _lot_ of optimizations,
> behaviors and functionality^[`Unit of Work` won't work, neither will change tracking, optimistic concurrency you''l have
> to deal with manually, etc.s] and is a real shame.
### The Async Session

So far, we have shown only synchronous work with the client API. But async support is crucial for high performance
Expand Down Expand Up @@ -1405,3 +1435,167 @@ Modifying the metadata in this fashion is possible, but it is pretty rare to do
usually use listeners (covered in [Chapter 4](#client-api)) to do this sort of work.

## Testing with RavenDB

This chapter is quite big, but I can't really complete the basics without discussing testing. When you build an application,
it is important to be able to verify that your code works. That has become a pretty much accepted reality, and an application
using RavenDB is no exception.

In order to aid in testing, RavenDB provides the `Raven.TestDriver` Nuget package. Using the test driver, you can get an
instance of an `IDocumentStore` that talks to an in memory database. You tests are very fast, don't require you to do complex
state setup before you start and are isolated from one another.

Listing 2.20 shows the code for a simple test for saving and load data from RavenDB.

```{caption="Basic CRUD test using RavenDB Test Driver" .cs}
public class BasicCrud : RavenTestDriver<RavenExecLocator>
{
public class Play
{
public string Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
}
[Fact]
public void CanSaveAndLoad()
{
using (var store = GetDocumentStore())
{
string id;
using (var session = store.OpenSession())
{
var play = new Play
{
Author = "Shakespeare",
Name = "As You Like It"
};
session.Store(play);
id = play.Id;
session.SaveChanges();
}
using (var session = store.OpenSession())
{
var play = session.Load<Play>(id);
Assert.Equal("Shakespeare", play.Author);
Assert.Equal("As You Like It", play.Name);
}
}
}
}
```

There are just two interesting things in the code in Listing 2.20. The code inherits from `RavenTestDriver<RavenExecLocator>`
class, and it uses the `GetDocumentStore` method to get an instance of the document store. Let us break apart what is going on.

The `RavenTestDriver<T>` class is the base test driver, which is responsible for setting up and tearing down databases. All
your RavenDB tests will use this class as a base class^[Not strictly necessary, but that is the easiest way to build tests.].
Most importantly from your point of view is that the `RavenTestDriver<T>` class provides the `GetDocumentStore` method, which
generate a new in memory database and is responsible for tearing it down at the end of the test. Each call to the
`GetDocumentStore` method will generat a _new_ database, running purely in memory, but other then that, fully functional and
behaving in the same manner as a typical RavenDB server.

If you paid attention, you might have noticed the difference between `RavenTestDriver<RavenExecLocator>` and
`RavenTestDriver<T>`. What is that about? The `RavenTestDriver<T>` uses its generic argument to find the `Raven.Server.exe`
executable. Listing 2.21 shows the implementation of `RavenExecLocator`.

```{caption="Letting the RavenTestDriver know where the Raven.Server exec is located"}
public class RavenExecLocator : RavenTestDriver.Locator
{
public override string ExecutablePath =>
@"d:\RavenDB\Raven.Server.exe";
}
```

The code in Listing 2.20 is using `xunit` for testing, but there is no dependecy on the testing framework from
`Raven.TestDriver`, you can use whatever testing framework you prefer.

> **How does Raven.TestDriver works?**
>
> In order to provide fast tests and reduce enviornment noise, the test driver runs a single instnace of the RavenDB server
> using in memory only node binding to `localhost` and a dynamic port. Each call to the `GetDocumetnStore`
> method will then create a new database on that single server instance.
>
> When the test is closed, we'll delete the database, and when the test suite is over, the server instance will be closed.
> This provides you with a test setup that is both very fast and that runs the exact same code as the real production code.
### Debugging tests

Sometimes a test fails, and you need to figure out what happened. This is easy if you are dealing with in memory state only,
but it can be harder if your state resides elsewhere. The RavenDB test driver provides the `WaitForUserToContinueTheTest`
method to make that scenario easier. Calling this method will stop the current test and open the RavenDB studio, allowing you
to insepct, validate and modify the content of the in memory database (while the test is _still running_).

This make is much easier to figure out what is going on, because you can just _look_. Lets test this out, add the following
line between the two sessions in the code in Listing 2.20 and then run the test:

WaitForUserToContinueTheTest(store);

When the test read this line, a magical thing will happen, shown in Figure 2.13. The studio will open, and you'll be able to
see and interact with everything that is going on inside RavenDB. One really nice feature that I like is in complex cases is
the ability to just export the entire database to a file, which let me import it into another system for later analysis.

![Peeking into running test instance, mid test](./Ch02/img13.png)

At this time, the rest of the test is suspended, waiting for you to confirm that you are done peeking inside. You can do that
by clicking the button shown in Figure 2.14, after which your test will resume normally and (hopefully) turn green.

![Press this to continue the test](./Ch02/img14.png)

The test driver can do quite a bit more (configure the database to your specification, create relevant indexes, load initial
data, etc), you can read all about it in the online documentation.

## Summary

At this point in the book, we have accomplished quite a lot. We started from setting up a development instance of RavenDB on
your machine^[The steps outline in this chapter are meant to be quick and hassle free, not to discuss proper production
deployments, check [Chapter 19](#prod-deploy) for details on those.] And how to set up a new database and played a bit with
the provided sample database.

We then moved to the most common tasks you'll usually have to go through with RavenDB:

* Creating / editing / deleting documents via the studio.
* Querying for documents in the studio.

The idea is to get you familiar with the basics of working with the studio so you can see the results of your actions and learn
to naviagate through the studio just enough to be useful. We'll cover more information about working with the studio in the
rest of the book, but the details of working with the studio are covered extensively in the online documentation, and unlikely
to need additional verbitage.

Things got more interesting when we started working with the RavenDB API and wrote our first document via code. We looked at
the very basics of defining entities to work with RavenDB (the next chapter will cover this exact topic in depth), creating and
querying documents and were able to remind ourselves to buy some milk using RavenDB.

We dove deeper and discussion the architecture of the RavenDB client, the use of `Document Store` and the `Document Session` to
access the cluster and a specific database, respectively. As a reminder, the document store is the singleton acces spoint to a
particular RavenDB cluster, and allows you to configure a wide range of behaviors globally by change the default conventions.

The session is a single `Unit of Work` that represent a single business transaction against a particular database and is the
most commonly use API to talk to RavenDB. It was designed explicitly to make it easy to handle the 90% of pure CRUD scenarios
and more complex scenarios are possible by accessing the `session.Advanced` functionality.

For the client API we moved to discussing how RavenDB deals with the crucial task of properly generating document identifiers.
We looked at a few of the RavenDB identifier generation strategies and how they work in a distributed cluster:

* HiLo - generates the identifer on the client by collaborating with the server to reserve identifiers ranges that can be
exclusively generated by a particular client.
* Server Side - generate the identifier on the server side, optimized for very large tasks, and allow each server to generate
human readable unique identifiers independently of one another.
* Identity - geneartes a consecutive numeric value using a consensus of the entire cluster. Typically the slowest method to use
and only useful if you _really_ need to generate consecutive ids for some reason.

You can also generate the document id yourself, which we'll typically call semantic ids. Those are ids that have some meaning,
maybe it is an external id brought over from another system or a naming convention that implies what is the content of the
document.

Document metadata was briefly discussed, allowing you to store out of band information about the document (auditing details,
workflow steps, etc) without impacting its structure. You can modify such metadata seamlessly on the client (and access it on
the server). RavenDB make use of the metadata to pass criticial information, the document collection, its id, etc.

Our last, by certainly not least, topic was discussion of testing your applications and how the RavenDB test driver allows you
to easily setup in memory instances that will let you run your code against them. The test driver even allows you to stop a
test midway through and inspect the running RavenDB instance using the studio.

In this chapter we starting building the foundation of your RavenDB knowledge and in the next one we'll start building on top
of it. We'll discuss modeling data and documents inside RavenDB and how to best structure your system to take advantage of what
RavenDB has to offer.
Binary file added Ch02/img13.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Ch02/img14.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 4 additions & 5 deletions Intro/Intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,11 @@ and concepts in favor of getting things done. This is what you'll want new hires
with an application using RavenDB, we'll keep the theory and the background information for the next part.

*[Chapter 2](#zero-to-ravendb) - Zero to RavenDB -* focuses on setting you up with a RavenDB instance, introduce
the studio and some key concepts and walk you through the Hello World equivalent of using RavenDB by building a
very simple To Do app.
the studio and some key concepts in utilizing it. The client API and its basic usage is also cover, as well as document
identifier generation and test RavenDB based application.

*[Chapter 3](#crud) - CRUD operations -* discusses RavenDB the basics of connecting from your application to
RavenDB and the basics that you need to know to do CRUD properly. Entities, documents, attachments, collections
and queries.
*[Chapter 3](#modeling) - Modeling documents -* Goes in depth into how to structure your entities and documents, the impact
of various RavenDB features on the structure of your documents and how you should handle common modeling scenarios.

*[Chapter 4](#client-api) - The Client API -* explores more advanced client scenarios, such as lazy requests,
patching, concurrency, listeners, bulk inserts, and streaming queries and using persistent subscriptions. We'll talk a about
Expand Down

0 comments on commit 4627c30

Please sign in to comment.