SharePoint & Domain Driven Design

SharePoint & Domain Driven Design

data: 3 lutego, 2014
czas czytania: 11 min
autor: Błażej Andraszyk

During the DDD training and then the Systems architecture one, the prevailing opinion on SharePoint was that in the case of this extraordinary platform there is no point in making the DDD implementation. While thinking about efficiency of our „monstrosity” the only word that comes to one’s mind is „madness”. Nevertheless, while developing another two projects for our German client, we decided to take this challenge.

Beginnings

First conclusions concerning changes in the way of implementing SharePoint projects came up after developing the first project providing service for complaints management for the same client. The solution based on SharePoint objects turned out to be incredibly difficult in maintenance. Additional aversion was developed to the usage of LINQ with SharePoint, which was supposed to only dissociate a little from the SharePoint model but instead it caused serious efficiency problems. But as it is sometimes: everything looks great, but only on MSDN. Within the time between projects we started to prepare our own solution, which would speed up implementation time and, most of all, widen implementation to unit tests of a designed domain.

Dependency Injection

Firstly, we considered DI mechanisms which could be implemented. In case of SharePoint there is one solution “embedded” (a part of Microsoft Guidance Library): SharePoint Service Locator. Unfortunately, it wasn’t fully satisfying solution. Implementation of the first system required designing intranet application created with the use of MVC, which would use SharePoint as a database. The Internet came with help and quite quickly we managed to dig into implementation of Autofac integration dedicated to SharePoint[1]. The solution turned out to be a bullseye – ultimately, Autofac can be used everywhere, even if we had to write console application. It almost looked that colourful. Firstly, we had to deal with refactoring and rewriting a part of the code for our purposes. Mostly, it was caused by the fact that in case of problems, we would be responsible for the code. Incidentally, we improved a few things, we got rid of commented code and used a few patterns. As a result of all those operations we created a project: FP.SharePoint.Autofac, and in its inside the most important for us, ServiceLocator class.

Its exemplary usage is as follows:

using (var locator = new ServiceLocator(properties))
{
    var queryHandler = locator.GetService();
    var commandGate = locator.GetService();

    (...)
}

Yes, it is correct, it looks like SharePoint Service Locator, however it works in a completely different way. SharePoint Service Locator enables to configure modules at two levels: SPFarm and SPSite. In our solution we have ability to configure modules at each level of SharePoint architecture: SPWeb, SPSite, SPWebApplication and SPFarm. Principle of operation is cascaded, it means that: downloading correlations for SPWeb we get correlations saved on all higher objects, so also for SPSite, SPWebApplication and SPFarm; however, downloading correlations for SPWebApplication only correlations for SPFarm will be additionally downloaded.

A diversion of this solution would be introducing priorities for particular configurations, which would enable more dynamic control over injecting for the whole farm, modifying particular SPWebApplication or even the smallest SPWeb.

Implementation of stable DI module allowed us to focus on the next part, i.e. access to data stored at the SharePoint end.

Repository & Context & Mapper

Access to data stored at the SharePoint end can be granted in a few ways [2]. We were interested in two of them: SSOM(Server-Side Object Model) and CSOM (Client Side Object Model). The former used directly for typical SharePoint solutions and the latter for external applications. It directly resulted in the mentioned system:

  • SharePoint application consisting data structures, which enables handling of reported requests,
  • Internet application used by employees to register requests.

Adopted model, most of all, took into consideration benefits from reduced demand on SharePoint access licences. Our aim was to create implementation which will consider both applications in one, universal solution – ultimately, we use mutual data source.

Firstly, we started with describing responsibilities of repository by specifying interface (below you cans see the final version, which was rather indigent in its previous form):

public interface IRepository
{
    T CreateItem(T item) where T : Entity, new();
    T CreateItemInsideFolder(T item, string leafName, string displayName) where T : Entity, new();
    T ReadItem(int itemId) where T : Entity, new();
    T UpdateItem(T item) where T : Entity, new();
    void DeleteItem(T item) where T : Entity, new();

    T GetSingle(IQuery query, QueryOptions options = null) where T : class, IDataTransferObject;
    IEnumerable GetCollection(IQuery query, QueryOptions options = null) where T : class, IDataTransferObject;
    int Count(IQuery query, QueryOptions options = null) where T : class, IDataTransferObject;
}

Methods were deliberately divided into two separate parts. That is how, we can notice that they operate on different types of objects:

  • Entity (FP.SharePoint.DomainArchitecture.Entity) – objects used within the frame of domain to business operations
  • DataTransferObject (FP.SharePoint.DomainArchitecture.IDataTransferObject) – objects used outside domain to present data

Interface specified this way has two implementations: Repository (SSOM) and ClientRepository (CSOM). Without delving into the code I should mention the two objects used within the repository: Context and Mapper.

The former is a direct reflection of the SharePoint objects at the server end and their equivalents at the client’s end. Both contexts differ when it comes to the manner of performing operations. Regarding server model everything is available while screening further objects, for client’s code objects have to be openly downloaded by developer. Using the repository covers up both implementations. Developer using IRepository interface doesn’t need to worry about calling operation in an appropriate order, no matter if it is client or server version. As in the case of repository two separate implementations were created: Context (SSOM) and ClientContext (CSOM).

The second mentioned element is mapper, the purpose of which is to ensure transformation of entities and DTOs from/to SharePoint objects. Here we also won’t delve into separate code lines but we will focus on solutions directly available to developer. First, a programmer has to implement and configure entity:

[ListInformation("Lists/Ships")]
internal class ShipEntity : Entity
{
    [FieldInformation("{9ae825d0-0a51-4e08-9c28-78399f2ee8e1}", "ShipCode")]
    internal string Code { get; set; }
}

And possibly, its equivalent in the presentation layer:

[ContentTypeInformation("0x0100AB71A4978CF244E59AB0D284C345BC0B")]
[ListInformation("Lists/Ships")]
[Serializable]
public class Ship : IDataTransferObject
{
    [FieldInformation("{1d22ea11-1e32-424e-89ab-9fedbadb6ce1}", "ID")]
    public int Id { get; set; }

    [FieldInformation("{fa564e0f-0c70-4ab9-b863-0177e6ddd247}", "Title")]
    public string Title { get; set; }

    [FieldInformation("{9ae825d0-0a51-4e08-9c28-78399f2ee8e1}", "ShipCode")]
    public string Code { get; set; }
}

We can notice following attributes which describes objects:

sharepoint 1

The most developed and interesting configuration is the one concerning fields and mappers. Developers can create their own mappers (they must be implemented separately for client and server side). If a developer doesn’t decide to use his/her own mapper implementation, a standard mapper, mapping basic SharePoint fields, will be used. Mapping lookup fields is a special case, which differs depending on object type, namely:

  • In case of entity, objects that inherit from above mentioned Entity class, lookup fields might be mapped to other entities, which will result in related objects tree.
[ListInformation("Lists/PortDays")]
internal class PortDayEntity : Entity
{
    (...)

    [FieldInformation("{ed2fd7ec-19f4-4fef-a701-bd2c83c4f2c6}", "ShipLookup")]
    internal ShipEntity Ship { get; set; }

    [FieldInformation("{f757b35a-a8a5-4e12-8fbb-b62b452f7df5}", "ArrivalTime")]
    internal DateTime? ArrivalTime { get; set; }

    [FieldInformation("{7fee8e1a-5490-4b16-8169-911967d38ad0}", "DepartureTime")]
    internal DateTime? DepartureTime { get; set; }

    (...)
}
  • In case of DTO objects implementing above mentioned IDataTransferObject interface, lookup fields might be implemented to int fields (respectively LookupId) or string fields (respectively LookupValue).
[ListInformation(SharePointModel.Lists.Urls.PortDays)]
[Serializable]
public class PortDay : IDataTransferObject
{
    (...)

    [FieldInformation("{ed2fd7ec-19f4-4fef-a701-bd2c83c4f2c6}", "ShipLookup")]
    public int ShipId { get; set; }

    [FieldInformation("{ed2fd7ec-19f4-4fef-a701-bd2c83c4f2c6}", "ShipLookup")]
    public string ShipTitle { get; set; }

    [FieldInformation("{f757b35a-a8a5-4e12-8fbb-b62b452f7df5}", "ArrivalTime")]
    public DateTime? ArrivalTime { get; set; }

    [FieldInformation("{7fee8e1a-5490-4b16-8169-911967d38ad0}", "DepartureTime")]
    public DateTime? DepartureTime { get; set; }

    (...)
}

Mapping lookup fields enabling a lot of values is identical with one exception, we have to use objects arrays.

To write our own mapper, it is sufficient to write class implementing one of interfaces:

  • IPropertyMapper – for server mapper,
  • IClientPropertyMapper – for client mapper.

Both interfaces are available under namespace FP.SharePoint.DomainArchitecture.Infrastructure.PropertyMappers. Below code presents exemplary mapper:

public class CRMPropertyMapper : IPropertyMapper
    {
        public object MapToItem(
            IRepository repository,
            ISharePointContext context,
            PropertyInfo property,
            object item,
            SPListItem sharePointItem,
            SPField field,
            object itemValue)
        {
            return itemValue == null ? null : EntityInstanceIdEncoder.DecodeEntityInstanceId(itemValue.ToString())[0];
        }
        public object MapFromItem(
            IRepository repository,
            ISharePointContext context,
            PropertyInfo property,
            object item,
            SPListItem sharePointItem,
            SPField field,
            object itemValue)
        {
            if (itemValue == null)
                return null;

            var cabinRequest = item as Domain.CabinRequestEntity;

            if (cabinRequest != null)
                sharePointItem[Fields.Client.Guid] = string.Format(CultureInfo.CurrentCulture, "{0} {1}", cabinRequest.PersonFirstName, cabinRequest.PersonLastName);
            
            return EntityInstanceIdEncoder.EncodeEntityInstanceId(new object[] { itemValue });
        }
    }

In order to classify mapper to a given field it is enough to give its type while determining property of mentioned FieldInformation attribute:

[FieldInformation("{DF1D4FCE-19AD-4497-B956-9BE4FCCBC575}", "Client_ID", typeof(Infrastructure.CRMPropertyMapper), typeof(Infrastructure.CRMClientPropertyMapper))]
internal string PersonCRM { get; set; }

QueryHandler & CommandGate

Before passing to designing domain we have to stop at two more classes, which will help us to completely separate domain form the SharePoint code. Those are already mentioned ones:

  • QueryHandler – a class responsible for getting data from SharePoint on the basis of queries,
  • CommandGate – a class responsible for calling CommandHanlders at domain end.

Firstly, let’s handle our QueryHandler. It handles 3 methods mentioned in case of repository, operating on objects implementing IDataTransferObject interface. As a result, we are able to return single records, collection or check how many elements fulfilling search criterion we have. Additionally, mechanism was constructed in a way that written queries would have possibility to fulfil requirements concerning efficiency described by Marcin Róg in his article [3]. Functioning is simple:

var myBookings = this.QueryHandler.GetCollection(new MyBookingRequest(this.GetSharePointUser().Id));

As a result we get a collection of all bookings assigned to me – logical.

Each query must implement IQuery interface, which was implemented in the basic QueryBase class. Exemplary query:

public class MyBookingRequests : QueryBase
    {
        private con
st string QueryPattern =
                    @";
                        
                            
                            {0}
                                               
                   ";

        private readonly int userId;

        public MyBookingRequest(int userId)
        {
            this.userId = userId;
        }

        public string Query
        {
            get
            {
                return string.Format(CultureInfo.CurrentCulture, QueryPattern, this.userId);
            }
        }

        public override string ViewFields
        {
            get
            {
                return "";
                    + "";
                    + "";
                    + "";
                    + "";
            }
        }
    } 

Another class is CommandGate, which enables to call particular business operations that will be handled by existing handlers:

this.Gate.Dispatch(new Application.Commands.CancelBookingRequest(this.RequestId.Value));

Our command must to inherit one interface from ICommand or ICommand (if we expect results of the operation):

public class CancelBookingRequest : ICommand
{
    public CancelBookingRequest(int id)
    {
        this.Id = id;
    }

    public int Id { get; private set; }
}

In turn, handler implementation is as follows:

internal class BookingRequestHandler : 
    (...)
    ICommandHandler,
    (...)
{
    (...)
    public void Handle(CancelBookingRequest command)
    {
        this.YieldDepartment.CancelBookings(command.Id);
    }
    (...)
}

It is known that DI must be configured – we do this through modules; example:

public class BookingDomain : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);

        // Basic
        builder.RegisterType().As();

        // Policies
        builder.RegisterType().As()
            .PropertiesAutowired().InstancePerDependency();
            
        // Factories
        builder.RegisterType().PropertiesAutowired().InstancePerDependency();

        // Handlers
        builder.RegisterType()
            .As()
            .PropertiesAutowired()
            .InstancePerLifetimeScope();
            
        // Services
        builder.RegisterType().As().InstancePerLifetimeScope()
            .PropertiesAutowired().EnableInterfaceInterceptors();
        builder.RegisterType().InstancePerLifetimeScope().PropertiesAutowired();            
    }
}

Where we have domain in all of this?

Whole domain is behind CommandGate and QueryHandler. I don’t want to go into details of particular implementations, I’d rather proceed to conclusions. Both projects have been developed independently, and we wanted to compare solutions at the very end. The result was good, because it turned out that our solutions are close to each other. The fact is that our domains weren’t complex – projects were rather experimental. Cooperation of above described Building Blocks, about which this article is, with the domain is presented in the below diagram:

sharepoint2

Benefits of used architecture were noticed very quickly. After analysing requirements we came into conclusion that in case of an exemplary operation of demand cancellation (calling operations visible on listing with CommandGate) deleting all existing reservations is an erroneous solution. It happened because there might have been related records in an external reservation system, which should be deleted manually. The change of behaviour in business server enabled us to change behaviour both at the server as well as client’s application end.

For the first attempt we can say that the result is very positive. Naturally, after a while some minor problems appeared, which have to be corrected and we try to handle this. At the end I insert a screen from working application:

sharepoint3

Links

[1] Autofac.Integration.SharePoint – https://github.com/techniq/Autofac.Integration.SharePoint
[2] SharePoint APIs – http://msdn.microsoft.com/en-us/library/office/dn268594.aspx
[3] Marcin Róg, PERFORMANCE WITH SHAREPOINT 2010 LARGE LISTS, /sharepoint

Newsletter IT leaks

Dzielimy się inspiracjami i nowinkami z branży IT. Szanujemy Twój czas - obiecujemy nie spamować i wysyłać wiadomości raz na dwa miesiące.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.