简体   繁体   中英

Where should I place business logic when using RavenDB

I am planning on building a single page application(SPA) using RavenDB as my data store.

I would like to start with the ASP.NET Hot Towel template for the SPA piece.

I will remove the EntityFramework/WebApi/Breeze components and replace with RavenDB for storage and ServiceStack for building the backend API.

Most current opinions seems to frown upon using any sort of repository or additional abstraction on top of RavenDB and call for using the RavenDB API directly inside of controllers(in an MVC app)

I am assuming I should follow the same wisdom when using Raven with ServiceStack and make calls against IDocumentSession directly inside of my service implementations.

My concern lies with the fact that it seems my service implementation will become rather bloated by following this path. It also seems that I will often need to write the same code multiple times, for example, if I need to update a User document within several different web service endpoints.

It also seems likely that I will need to access Raven from other (future) pieces of my application. For example, I may need to add a console application that processes jobs from a queue in the future, and this piece of the app may need to access data within Raven...but from the start, my only path to Raven will be through the web service API. Would I just plan to call the web api from this theoretical console app? Seem inefficient if they are potentially running on the same hardware.

Can anyone offer any advice on how to utilize Raven effectively within my webservices and elsewhere while still following best practices when using this document store? It would seem practical to create a middle business logic tier that handles calls against raven directly...allowing my webservices to call methods within this tier. Does this make sense?

EDIT

Can anyone provide any recent samples of similar architecture?

FWIW, we're currently working on an app using ServiceStack and RavenDB. We're using a DDD approach and have our business logic in a rich Domain Layer. The architecture is:

  1. Web App. Hosts the web client code (SPA) and the service layer.

  2. Service Layer. Web services using ServiceStack with clean/fairly flat DTOs that are completely decoupled from the Domain objects. The Web Services are responsible for managing transactions and all RavenDB interaction. Most 'Command-ish' service operations consist of: a) Load domain object(s) (document(s)) identified by request, b) Invoke business logic, c) Transform results to response DTOs. We've augmented ServiceStack so that many Command-ish operations use an automatic handler that does all the above without any code required. The 'Query-ish' service operations generally consist of: a) Executing query(ies) against RavenDB, b) Transforming the query results to response DTOs (in practice this is often done as part of a), using RavenDB during query processing/indices/transformers). Business logic is always pushed down to the Domain Layer.

  3. Domain Layer. Documents, which correspond to 'root aggregates' in DDD-speak, are completely database agnostic. They know nothing of how they are loaded/saved etc. Domain objects expose public GETTERs only and private SETTERs. The only way to modify state on domain objects is by calling methods. Domain objects expose public methods that are intended to be utilised by the Service Layer, or protected/internal methods for use within the domain layer. The domain layer references the Messages assembly, primarily to allow methods on our domain objects to accept complex request objects and avoid methods with painfully long parameter lists.

  4. Messages assembly. Standalone assembly to support other native .Net clients such as unit-tests and integration tests.

As for other clients, we have two options. We can reference ServiceStack.Common and the Messages assembly and call the web services. Alternatively, if the need is substantially different and we wish to bypass the web services, we could create a new client app, reference the Domain Layer assembly and the Raven client and work directly that way.

In my view the repository pattern is an unnecessary and leaky abstraction. We're still developing but the above seems to be working well so far.

EDIT

A greatly simplified domain object might look something like this.

public class Order
{
    public string Id { get; private set; }
    public DateTime Raised { get; private set; }
    public Money TotalValue { get; private set; }
    public Money TotalTax { get; private set; }
    public List<OrderItem> Items { get; private set; }

    // Available to the service layer.
    public Order(Messages.CreateOrder request, IOrderNumberGenerator numberGenerator, ITaxCalculator taxCalculator)
    {
        Raised = DateTime.UtcNow;
        Id = numberGenerator.Generate();
        Items = new List<OrderItem>();
        foreach(var item in request.InitialItems)
            AddOrderItem(item);
        UpdateTotals(taxCalculator);
    }

    private void AddOrderItemCore(Messages.AddOrderItem request)
    {
        Items.Add(new OrderItem(this, request));
    }

    // Available to the service layer.
    public void AddOrderItem(Messages.AddOrderItem request, ITaxCalculator taxCalculator)
    {
        AddOrderItemCore(request);
        UpdateTotals(taxCalculator);
    }

    private void UpdateTotals(ITaxCalculator taxCalculator)
    {
        TotalTax = Items.Sum(x => taxCalculator.Calculate(this, x));
        TotalValue = Items.Sum(x => x.Value);
    }
}

There's two main parts to consider here.

Firstly, as you have already noted, if you go by the word of the more fanatical RavenDB fans it is some mythical beast which is exempt from the otherwise commonly accepted laws of good application design and should be allowed to permeate throughout your application at will.

It depends on the situation of course but to put it simply, if you would structure your application a certain way with something like SQL Server, do the same with RavenDB. If you would have a DAL layer, ORM, repository pattern or whatever with a SQL Server back-end, do the same with RavenDB. If you don't mind leaky abstractions, or the project is small enough to not warrant abstracting your data access at all, code accordingly.

The main difference with RavenDB is that you're getting a few things like unit of work and the ORM for 'free', but the overall solution architecture shouldn't be that different.

Second, connecting other clients. Why would a console app - or any other client for that matter - access your RavenDB server instance any differently to your web site? Even if you run the server embedded mode in your ASP.NET application, you can still connect other clients to it with the same RavenDB.Client code. You shouldn't need to touch the web service API directly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM