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:
Basically, it should provide:
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:
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."
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.
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).
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.
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:
An example repository implementation: NavbarRetrieveRepository
Service Implementation
And NavbarRetrieveService is the place to add logic and it calls NavbarRetrieveRepository to retrieve data from database. NavbarRetrieveService implementation is like this:
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 :
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:
Hope, it helps someone.
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)
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
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!
Hope, it helps someone.
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.
ReplyDeleteThanks! It is good to hear that it works well in a real project.
DeleteThis comment has been removed by the author.
ReplyDeletecan you please share the sample source code?
ReplyDelete