Skip to main content

Repository & Service Pattern Experiment in Sitecore

I know it is assertive topic for first post, but maybe it is better to be assertive from the beginning! My plan is to keep here with as possible as challenging topics, discussions and experiments.

Let's begin.

Why Repository & Services Pattern instead of Modular Architecture?

First, personal reason:
I have experience with Repository & Service Pattern and not with Modular Architecture! I've recently worked for a long term custom big project (not Sitecore project), used this approach and saw its capabilities. During the development, we have faced too many requirement changes, new features etc. We could manage quite well with this approach. I couldn't see Modular Architecture capabilities for a such project yet.

Second, technical perspective:
Although, I haven't seen Modular Architecture capabilities on a real big project, when I have a look to Habitat and other implementations, I see some difficulties:
  • Difficult to decide modules
  • Big requests or requirement changes may effect modules' structure
  • Web APIs (maybe it is easy to manage but I haven't tried a complex scenario yet)  
What do we expect from architecture in a project?
Basically, it should provide:
  • Easy to manage
  • Easy to extend
  • Easy response to requirement changes
  • Easy to test 

Repository & Service Pattern

What is Repository Pattern?
Formal definition:
"Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects."

We may define it as data access layer:
  • Minimizes duplicate query logic 
  • Provides better separation of concerns
  • Decouples from persistence frameworks 
Sitecore specifically, we can think that repositories are the only places for database calls. Querying, eager loading, filtering are the concerns of repositories.

But serving ready to use data for presentation is not a repository concern! Repositories should only provide your model data. You shouldn't add presentation specific logic to your repositories. You may have multiple presentation layers and they may need different data, you need to create presentation specific data somewhere else, for example, in services.

Services
Formal definition:
"Service layer is the middle layer between presentation and data store. It abstracts business logic and data access. The idea behind such a layer is to have an architecture which can support multiple presentation layers such as web, mobile, etc."


Why do we need such a layer? Isn't it possible to call repositories directly from controllers (MVC pattern)? 
It is possible to call repositories directly from controllers. General approach for this will be calling and retrieving data from repository, then adding logic, creating view model and sending it to View.

Then, why do we need services?
The key to answer this question will be the highlighted text in definition; support multiple presentation layers.

Instead of adding logic to controllers, the logic will be in services. Services will be the only places to call repositories.

Imagine that you have started a project and at the beginning you had just one website. And you added logic to controllers, you were calling repositories from controllers. Six month passed and the client requested an app which would use almost same data with web. So, you needed to add APIs to provide data to apps.
Oops! You've added all logic to controllers, would you duplicate logic for API controllers!?

If you had logic in services at the beginning, it would be very easy for you to serve necessary data to API controllers.

Service & Repository Relation
An example relation between repositories and services is below:

As you see above, repositories will not talk to each other, they all will work independent. The communication will only be between services. Above example, PaymentExecutionService needs to retrieve a customer. It doesn't call directly to CustomerRepository! It requests from regarding service, in here, it is CustomerRetrieveService. Of course, all is based on abstractions, interfaces! I do not need to mention it.

Sitecore Implementation

As I mention above, I have used this architecture for a custom build project before, not for a Sitecore project. I thought this is more generic approach and can be applied to different kind of projects, why not for Sitecore?

In previous project, Entity Framework was used as ORM and our model was in Datalayer. We had an another layer called "Definition" and all Enums, Constants, Resources were defined there (as separated folders).

This structure helped a lot during the 2 years development! So, I've decided to use for Sitecore, too.

Here is the general architecture view:

This architecture more looks like classical database centric architecture. Generally,  CMS based solutions have database centric architecture rather than domain centric. (I haven't seen a domain centric CMS based solution yet).

A Scenerio for Capabilities of This Architecture

I assumed that repository and service pattern is very useful for Sitecore, too. But I needed to see if it really works well or not.

I imagined that I had a project and it was just a one website at the beginning. I will call it as "Site-1". I have started Site-1 with repository & service architecture. I have implemented the project and released the website. Here is the view of website:

All good! 4 months later, customer requested an admin panel called "Portal" for a custom domain users. That Portal will share some data with Site-1. I have decided to implement this Portal as Area and I added an Area called Portal to my project.

Portal login:
Simple Portal dashboard:

After some time, customer came to me for a mobile app which will provide same functionality with Portal. I needed to add APIs to project and they were basically had the same logic as Portal web.

It's super easy! My logic has already been inside services. I added a new folder called Apis to Portal Area and added API controllers there, that's it!

Requests didn't finish! Customer wanted a new website called "Site-2" this time. No issues! All I needed to do was adding a different style for it.


Sitecore structure looks like this after adding Site-2:


Let's continue with project implementation.

Layers

Definition Layer:
It includes Contstants, Enums, all definition related classes. All layers have a reference of this layer.

DataLayer:
It includes Models, Extension methods, ViewModels, ApiModels. All layers except Definition has a reference of DataLayer.

I used Glass Mapper and TDS Code Generation to create models. But I have applied a quick trick here. Originally, TDS Glass Mapper code generation creates model classes in different namespaces. For example:



I didn't like it because let say you added this specific namespaces to classes and views (even if it is not recommended, you should use viewmodels). Then, for some reason you needed to change your ORM, because of different namespaces on your model, you have to change it everywhere. But if you have your model in a same namespace. Your job would possibly be more easy, you can generate new model using same namespace. I will explain and share code examples in another blog post soon. But my auto generated model classes looks like this:



They share DbContext namespace.

Repository Implementation
I have a base class which helps other repositories. Here is a quick preview of it:

namespace Training3.Repository.CommonRepositories
{
    public abstract class BaseRepository
    {
        public Item ContextItem => Sitecore.Context.Item;

        private readonly ISitecoreContext _sitecoreContext;

        protected BaseRepository(ISitecoreContext sitecoreContext)
        {
            _sitecoreContext = sitecoreContext;
        }

        public T GetContentItem<T>(Guid contentGuid) where T : class, IGlassBase
        {
            Assert.ArgumentNotNullOrEmpty(contentGuid.ToString(), "contentGuid");

            return _sitecoreContext.GetItem<T>(contentGuid);
        }


An example repository implementation: NavbarRetrieveRepository

using System;
using Glass.Mapper.Sc;
using Sitecore.Data.Items;
using Training3.Repository.CommonRepositories;

namespace Training3.Repository.LinkRepositories.NavbarRepositories
{
    public class NavbarRetrieveRepository : BaseRepository, INavbarRetrieveRepository
    {
        public NavbarRetrieveRepository(ISitecoreContext sitecoreContext) : base(sitecoreContext)
        {
        }

        public Item GetNavigationFolderItem(Guid navigationFolderId)
        {
            var result = GetContentItem(navigationFolderId);

            return result;
        }
    }
}

Service Implementation
And NavbarRetrieveService is the place to add logic and it calls NavbarRetrieveRepository to retrieve data from database. NavbarRetrieveService implementation is like this:

namespace Training3.Service.LinkServices.NavbarServices
{
    public class NavbarRetrieveService : BaseService, INavbarRetrieveService
    {
        private readonly INavbarRetrieveRepository _navbarRetrieveRepository;

        private readonly ISitecoreContext _sitecoreContext;
        public NavbarRetrieveService(INavbarRetrieveRepository navbarRetrieveRepository, ISitecoreContext sitecoreContext)
        {
            _navbarRetrieveRepository = navbarRetrieveRepository;
            _sitecoreContext = sitecoreContext;
        }

        public NavbarViewModel GetNavigationItems(Guid navigationFolderId)
        {
            var navigationFolderItem = _navbarRetrieveRepository.GetNavigationFolderItem(navigationFolderId);

            if (navigationFolderItem == null) return null;

            var navbarViewModelItemList = new List<NavbarViewModelItem>();

            foreach (Item scItem in navigationFolderItem.Children)
            {
                var modelItem = _sitecoreContext.Cast<Link_Item>(scItem);

                if (modelItem.Link.HasValue() && ID.Parse(modelItem.Link.TargetId) == ContextItem.ID)
                {
                    modelItem.IsActive = true;
                }

                navbarViewModelItemList.Add(new NavbarViewModelItem
                {
                    Title = modelItem.Navigation_Title,
                    IsActive = modelItem.IsActive,
                    Url = modelItem.Link?.Url,
                    Children = scItem.Children.Any() ? scItem.Children.Select(GetNavigationSubItem).ToList() : null
                });
            }

            var result = new NavbarViewModel() { NavbarViewModelItems = navbarViewModelItemList };

            var siteInfo = Sitecore.Context.Site.SiteInfo;

            if (siteInfo.Name.Equals(MultiSiteConstants.Site1Name))
            {

                var portal = Sitecore.Configuration.Factory.GetSite(MultiSiteConstants.Site1PortalName);

                result.PortalName = portal.Name;

                result.PortalUrl = $"{portal.SiteInfo.Scheme}://{portal.TargetHostName}";  
            }

            return result;
        }

At the end, your controllers will be so clean. When you need to add a control or change something on your logic, you will know that the place that you need to look is services, because all logic is in services.

Here is the view of NavbarController :

namespace Training3.Web.Controllers
{
    public class NavigationController : BaseController
    {
        private readonly INavbarRetrieveService _navbarRetrieveService;

        public NavigationController(INavbarRetrieveService navbarRetrieveService)
        {
            _navbarRetrieveService = navbarRetrieveService;
        }

        public ActionResult Navbar()
        {
            var result = _navbarRetrieveService.GetNavigationItems(DataSourceItem.ID.ToGuid());

            return PartialView(result);
        }
    }
}

Project implementation looked like this at the end:



At this point, I want to remind principles of good architecture again which I mentioned at the beginning of this post.

A good architecture should provide:
  • Easy to manage: Yes, it provides good separation of concerns 
  • Easy to extend: I was able to add new sites, APIs easily without changing anything!
  • Easy response to requirement changes: Yup, customer wanted a lot of change and requests
  • Easy to test: All logic is in services, very easy to test!
Eventually, I tried with an example scenario, created a demo project and I saw that this pattern is easily applicable to Sitecore projects, too. 

Hope, it helps someone.

Comments

  1. Congrats for the hard topic! We also use this architecture in a Sc9 project. On top of it, we have code generation creating glass models, repositories and services, everything as partials so we can easily extend.

    ReplyDelete
    Replies
    1. Thanks! It is good to hear that it works well in a real project.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. can you please share the sample source code?

    ReplyDelete

Post a Comment

Popular posts from this blog

Sitecore 9 - Custom Page Events & Filtering with XConnect

This is the first article of a series. I am going to start with creating a custom page event and will show how we can fetch event data using xconnect api. Let's start with reminding demo scenario: Imagine that you have a website displaying movies. Visitors are able to see movie details and take some actions like save movie or share it.  You want to follow the visitors' activities and you want to take some marketing actions based on those activities. For example, if a contact visits a movie more than X time or she/he saves a movie, you want to send those movies to an external system. In addition, there is going to be a limit to send same movie. Such as, it will not be possible to send same movie more than 2 times.  You want to configure this as a marketing automation plan to give flexibility to your marketing managers. They should be able to add configurable rules and activities.  My first focus is movie detail page. I want to track visitors when they visit the

A React Example in Sitecore

That's true, Sitecore components are useful and provide lots of facilities. They allow to build fully flexible website. And there is not much problem using post-backs to display your contents. However, if your website has user interactions, then it could be a headache. Too many post-backs would occur a poor user experience. That's why client-side technologies are really powerful and popular for a long time. There are plenty of javascript libraries and frameworks which all try to provide better user experience in different ways. It is not a topic of this article why I chose React. There are some good articles about it (e.g. Jonne Kats' great article ). However, one of the reasons is React only focus on view part. It is not a fully framework like Angular, it is rich featured library instead. And that's what I was looking for! The idea was to benefit Sitecore server-side and React client-side power. It seems they are very good match! Let's dive into code... I&#

Sitecore 9 - Creating Custom Segmentation Rule

Predicates are important to create rules for marketing automation and segmented lists. Before going through the details, I am going to remind what I am implementing again. Imagine that you have a website displaying movies. Visitors are able to see movie details and take some actions like save movie or share it.  You want to follow the visitors' activities and you want to take some marketing actions based on those activities. For example, if a contact visits a movie more than X time or she/he saves a movie, you want to send those movies to an external system. In addition, there is going to be a limit to send same movie. Such as, it will not be possible to send same movie more than 2 times.  According to my scenario, I need a rule to filter contacts who visits a movie more than X time. Maybe, we can add one more condition there like in Y days. To creating rule will give flexibility to marketing managers that then they can easily add or remove multiple rules and proceed the