Avon.com: An Umbraco approach to a headless CMS
In this session, you will learn how to use Umbraco as a headless CMS an approach DeCare Systems Ireland (DSI) took as they saw it has brought value to their customers while allowing faster development cycle.
We will explain the difference between traditional and headless CMS on a practical example used on the Avon.com website. We’ll start by looking at the importance of the API and raw data focusing on the implementation details explaining modular structure and its benefits. After a deep dive into code we’ll finish outlining the scalability, applicability, extensibility and overall pros and cons of the headless CMS approach.
The goal of this session is to give you an overview of how easy it is to extend Umbraco and deliver flexible modular content components to provide real value to your customers.
View transcript
Welcome, everyone. So my name is Martin Fenton, and myself, my colleague, Matthew, and I will be presenting to you today on an approach that we took to the implementation of a headless Umbraka solution for AVEN.com. So just one thing to mention, first of all, we were here last year at CoGarden 2015, and we had submitted for an award under the best technical category for this AVEN.com solution, and we were proud to have come runner up. So that was great. We're happy with that. We said we'd come back this year, and we just spent some time to go through one particular technical aspect of that solution for you guys, see if there's anything from it that you could take away. So in this talk today, we will be discussing the headless CMS solution, some of the features of it. We'll be discussing some of the differences between the traditional approach and the headless approach. Hopefully, if Visual Studio works out for me, take you through some code. I'm sure you'll all be looking forward to seeing some of that. We'll also be outlining some of the pros and cons of the headless approach over the traditional approach, and Matthew will spend some time taking you through some plugins and custom sections that we've developed on this Umbraka solution as well. So before we get started on that presentation, I guess we'll introduce ourselves. So as I said, my name is Martin Fenton. I've been working as a .NET developer for more than 13 years. For the past six and a half years, I've been working for Decare Systems Ireland. My particular focus, I guess, over those six years has been e-commerce solutions. That changed a few years ago in that the clients that we work for wanted to get CMS solutions also integrated into these enterprise e-commerce solutions. So I have experience working with Umbraka, obviously. Prior to joining Decare, I also worked with SharePoint, and I also have worked with Sitecore as well, amongst others. So that's me. Just one thing to mention about Matthew, who's going to introduce himself now, is he got married on Friday. So I just want everybody here to join me to wish him a congratulations. Thanks. Congratulations, and all the best for your future. Thanks. Thanks. And fair play for coming to this talk. Yeah. I'd say he had a fight for his new wife. Yeah. So I'll let you just introduce yourself. I'm Matija Grčić. I'm from Croatia. I'm working for Decare Systems for about a year and a half. I'm currently working remotely for them, just visiting every couple of months on site. I'm mostly just a web developer. So I'm Microsoft certified for web solutions and Azure. I work before joining Decare. I mostly work for business applications for the government. But after joining Decare, I started working on the Avon project, and that's how I got introduced with Umbraka. I never heard of it before. I heard about Orchard, I think. That's the other CMS. But I've never used any of the CMSs before, except Warp and Web. Except WordPress on the college days. So that's me. So you started with the right one. Yeah. So I guess the first thing that we want to do is, well, just explain, well, what is a headless CMS and why would you want to take such an approach? So a headless CMS is where there's an API to the content. In our case, in Avon.com, it's a JSON API. So basically, you've got an application that is serving the content out in a way that's in JSON format, and it can be exposed to an array of front-end applications. So that's one of the potential benefits of the headless approach is that there's a multi-channel approach that you can take with regard to consuming it. You've also got this concept of a decoupled architecture, decoupled teams, where you can have developers who are heavily focused on back-ends, can focus on the back-end technologies. They've got the skills and experience for that, while you might have different front-end developers who've got skills that are more focused for front-end development, and that's becoming increasingly important, I guess, with all these newer technologies that are coming on stream over the past few years, some of which we use the likes of Angular, React, TypeScript, different things like this. So it can... And then, of course, you're trying to achieve the best possible end-user experience in the front-end, so that can be one of the benefits as well of taking a headless approach. So just to interrupt. In relation to the image that you see in front of you here, this headless Lego image, so I asked... My six-year-old son came across this image, and he was asking me about it, you know? So I turned around and I said to him, I said, well, David, why do you think you need headless Lego? And his response was actually quite simple. He said, so that you can change heads. Good answer. So I said, well, actually, as it happens, in terms of technology, I think it's a good That is one of the benefits of the headless approach. So you can change heads, so you can change the front-end without having to change the back-end. And again, in relation to front-end developments, you know, that's something that you're... You know, it's visible to the users. It's something that you might want to refresh more often than your back-end. If you've got an array of front-end applications that are consuming the content, then it could be useful that, you know, you can change any one of those front-ends without working on the back-end. So just to take that back then to the AVENO. com perspective. So AVENO.com have been a client of Dequeer Systems Ireland for, I don't know, I think it's about 15 years now. So a lot of the clients that we would have are long-term engagements, long-term clients. The evolution of the application suite that we developed for AVEN, you know, it's evolved a lot over the last 15 years. It started out as classic ASP, you know, .NET web forums. A kind of a hybrid NBC. It's a pretty easy approach prior to real ASP.NET NVC being introduced. So there's lots of... There's a big evolution in terms of the progress of that AVEN.com solution. And there was a WCF services suite introduced to AVEN.com about three and a half, four years ago. So the fact that you've got a multi-tier e-commerce solution like that is one of the reasons why we felt when the CMS objective... Became, you know, when we had... When that became apparent about two years ago, we felt it would be appropriate to take the headless approach because it just gave us that added benefit to being able to leave the WCF services suite that sat behind this new site that we're developing. We didn't need... We were able to make minimal changes to that while working on a new front end and on backward driving that new front end. So we'll be outlining a little bit more later on in terms of, you know, how we did that. Thank you. Thank you. So just to take you through some of the differences between a traditional approach and a headless approach. So with both the traditional and the headless approach, you've got obviously your data storage. So you've got a SQL server, for example, for your... As your content database. You've got your back office crowd authoring tool. You have that in both approaches. But it's when you come to the front end is where you see the differences. So in... With the traditional approach. All of your front end features are built into that one application. It displays the content. It pulls the content. It does... That one application does everything for you. Whereas in the case of the headless approach, as I mentioned already, you've got an API to the data. You've got... It's exposed in JSON format or whatever format you might like to expose it as. So that's some of the differences between those guys. So just to give a diagram maybe that just illustrates the traditional approach. So as I said, you've got one single application and all of your array of users are consuming this application. And just to compare that with the headless approach, then you've got the flexibility to expose the raw content in JSON format to your custom front end application. And again, just to outline from the AVEN.com perspective, this worked quite well for us. AVEN.com is quite a large enterprise e-commerce application. And it was advantageous for us to take the headless approach from a resource allocation perspective, setting up the servers. So it was an N-tier application suite. So we had the flexibility to deploy multiple applications. So you've got the front end UI application. You've got the Mbrak application. And you've got your WCF services suite. So there was some benefits to do it that way for us in AVEN.com. Okay. So I think now I'm just going to hand you over to Mattia. So Martin was saying that we have an API that provides all the data for the front end. So we chose to have an API so we have the flexibility of decoupling everything from the front end. So when you add your nodes to the Mbrak and you add the content there and we expose that with the API, then the front end developer doesn't need to really know. He or she just needs to know how to render the data. And previously, you'll see on the next slides, but when we started AVEN.com, we used Mbrako 6. So there was no Mbrako API. It was web forms. And when we first saw that Mbrako 7 will be Angular driven, we decided to use Handlebars.js for all the plugins, all the customization. So even back then, we started going with the approach to be completely decoupled. And it's like if you have a travel blog or a small site or just a site that you're doing to your friend, you wouldn't really go with the headless approach because it adds unnecessary complexity. Because then you need to really have skills in all the areas from the development side. But from the development point of view and from the creative or content management point of view. And because we're currently giving just the JSON structure, all the data is structured and everything has the same response. And I think Martin will go through that in the deep dive into code. So what do we mean with raw data now? Maybe some of you know, but when I think ASP.NET API was introduced, then the whole data was ecosystem started to be more hypermedia driven, so you have your hypermedia formatters and whatever you request using the accept header, which is a web standard, the application should return that content back. So if a user in the accept header requests a text plain or a text HTML, then your application needs to know what to return back. Currently we don't, we aren't hypermedia driven completely because we have the, like the JSON structure that turns out well for us. So because we have the raw data, we have the full control and we are multi-channel, so it doesn't matter which device consumes the content, so we just expose the data and then we have mobile developers, front-end developers who render the data on the necessary devices. We we put together a list of examples, so you know WordPress, that WordPress has a REST API, they had a JSON API, which is now deprecated, and Drupal. So WordPress and Drupal, they're big names in the CMS world, but they're also, they also notice that most of the sites that are not small, that are not smallish, needs something that to express just the data and then somebody can come in, some agency or some firm, and then render the data as rich and enrich the content. There's also attraction for the headless CMSs, so there's a Blackstar CMS and the Contentful, and they are both driven by the API from the start. They're, I think they're relatively new, so if you want you can check them out. There's also a REST API for Umbraco, which is in the Umbraco, and it's based on the REST API. GitHub repository, they're using the hypermedia application language, which is a, I think it's now a web standard, so it's, it's basically everything's a representation, so you can use generic, there's a cool generic navigation, I think it's a JavaScript application, so it's similar to how you, how people use it. You type a name to any website and you get the content and then you click on the links. So the hypermedia application language is the same. You get the front page with all the possible links and then when you visit one link, you get another list of links. And it's good because you can break the API. As soon as you put the name of the link and you don't change it, you will never break any of the existing applications that relates on your, on your API. So you can check it out. And the, the Umbraco REST API is using the web API HL, L hypermedia formatter, which is also open source and on GitHub. So how it works. I said that we started with the web forms application, so we're not using the web API for the, for getting the content from our front end. We're using the MVC controllers. So we have a controller MVC or web API. We have a provider. Providers get, know how to read the content from any of the notes. And we, we have a really good, really good generic structure that Martin implemented. So we, when you introduce a new document type, don't, you don't need to add any provider and then for that document type, build it from scratch. You, you reuse small bits and, and provider just goes, I just can go, you just goes recursively and reads the data, however they structure your nested nodes. And then you have a media type. We said that we are using JSON, but it can be anything based on the accept header. And then we have a render. That's a renderMvcController. And the renderMvcController is just a, I think it's a base class, like a base controller, and it has only one action. It's an index action. And from that, you just call the provider, which Martin will show, and you return the content that's needed for that page or that specific section of the web page. So just a quick summary for the Web API MVC. Web API is mostly called from the backend, and there are some direct calls from the website, but we're not really depending on the Web API for getting the content to the frontend. We use the MVC heavily because that was how we did it initially before the REST API from Umbraco version 7. So we have a base controller that inherits from the renderMvcController, and then we have an index action in every of the controllers. Controllers are convention-based, so if you have a document type named, I don't know, hero module or something, then we have a base controller that's called hero module controller. So everything's convention-based, and as soon as you add that controller, you get your templates. We don't have any of the templates in the Umbraco back office. Every template is just a JSON coming back, and then the frontend developer or whoever needs to render their data. So then you get the response, which is based on the media types. So Martin will now do the deep dive into code to explain it even further. Thanks, Mattia. So let's hope Visual Studio behaves itself for me, because it wasn't just before. So Mattia mentioned about the index action and returning JSON. So what you can see in front of you here is you can see your index action taken in the render model. You can see that this controller that we have here is called a page controller. So we've got a page document type. The page document type, the JSON for that has been rendered through this render MVC controller, not through what most Umbraco developers will be used to using, which is using the display templates that you configure within Umbraco. So this render MVC controller takes over instead of those templates and renders your content in JSON format. So I'm just going to try navigating through a set of bookmarks here just to see if I can just drill a bit deeper. Okay. So Mattia mentioned about the render MVC controller. So you can see that the base controller derives from that render MVC controller. You can see that we've got a published content property exposed from that controller, which is being pulled from an LPM context. Just to mention, LPM has nothing to do with Umbraco. It's the term that we coined ourselves. It just stands for landing page manager. So the LPM context is just a wrapper for some of the Umbraco API context objects that we may want to access for retrieving whatever particular content we want to retrieve. So you can see there you've got the likes of the Umbraco helper, the Umbraco context services, and so on. Taking you back then to the page controller, you're instantiating the page provider passing it in that context. So as we all know, the Umbraco context is for the particular nodes that you're looking at. If you're using Umbraco as the traditional approach, then you're addressing it at a particular page. So that's being routed to the appropriate API in the backend for that particular node. So basically, in our case here, what we're doing is we're instantiating the page provider. We're passing in the context and we're saying, we want you to access the content for us for this particular node context. And you'll see in a minute that as you drill through, a hierarchy of nodes, what we do is we change that context to switch to the particular child node or descendant node as you're drilling further down and building up a full, complete hierarchy of your content, which is then being returned in JSON format to the frontend. And you can see just towards the bottom of that there that there's this.json. So we're just serializing a page content object to JSON format. So this is the page provider and you can see that the LPM context is being passed into that. We're setting that into our provider. I'll just drill in a little bit further. So the content property is returning out a page entity. So all of, every single document type that we have inside the solution has a corresponding entity. And what we're doing is we're just populating that entity with the properties of the content for that particular node. So for example, in the case of a page, you've got some heading information, libraries, some SEO properties, and you've got a collection of modules. So when we're building up our site, we're building up modular component-driven UI. So it's a collection of modules is what we're building up within that page. And those collection of modules, are being returned in JSON form to the front end. And it's those modules that are then rendered on the front end. And we've done it in such a way as we're giving as much flexibility as possible to the content authors so that they can build up all of these modular components. So what drives this is the module container provider. All of these providers derive from a base provider. And the base provider just gives us some easy access to methods that allow us to pick up particular modules. So we can create a module container with a string, or a checkbox list, or the related links, for example, things like that. And the module container here is just our way of instantiating an entity that represents one of those modules. We've got a string property exposed off that module container entity. And what that is just is serialized JSON that corresponds to the content for the module. So for example, based on the document type alias, we know that the child node that we're looking at is a generic module. So we're instantiating a generic module passing in the context for that particular node. And then the generic module knows exactly what properties it needs to load in order to be able to return that content. Again here you can see the module container entity being instantiated. And we see the content property here. And that's what's being returned out of the Umbraco application that the frontend then consumes. So just to explain that in a little bit more detail, if we go to this page here, you can see that the URL that we're going to is slash shop en pages contact us. So the contact us page, the content flat has been returned in a JSON format. I'm just using JSON view here, just to give a pretty print for the JSON. You've got the SEO properties for the page. And then you've got a collection of modules. So each of the modules, you can see the content property has a set of serialized JSON. And that JSON is the full object model for your particular module. And that's what gets rendered in the frontend. So if we look at a page in the application. Sorry, one sec. So on the avon.com homepage, you've got a hero spot, which has a module. You've got another module underneath there for those brands. You've got a module for this enter for a chance to win. So everything you see here is a type of a module, maybe a generic module. Or for example, this particular grid-like structure is a generic grid module. You've got an additional generic module there for the can't miss offers. Buy one get one free is a product carousel module. And then a generic module. So you're building up a collection of modules that represent the content for the page. So go back to Visual Studio. Let's see. So I think I've probably taken you through everything in the backend in terms of what we're doing. So you've got the generic module entity being instantiated. You've got, oh yeah, so in terms of the generic module, it's a mechanism. The way we approached that was we wanted to keep it as generic as possible. So you could build up a collection of rows. Within each row, you've got a collection of stacks. And within each stack, then you've got a collection of images, rich text, what we refer to as primitives. So you can put whatever you want into any given stack. And it just gives you the most, as much flexibility as possible to the content author to build up the page. And whatever form that they want. So there's the stacks. So you can see properties in the stack entity for image items, rich text items, and so on. And you can see here an example of where we're getting an LPM context for the current node, which is the row. And we're passing that context into the row provider. And then we're accessing the content associated with that. And then it just continues down and drills into the hierarchy of nodes to build up your complete object model. So there's just an example of a method just that we would use to get any particular property value. And it's a series of those that are in the base provider that just help us to build up the entities that represent the content. Okay. So going back. So that was the backend. So in the frontend, we want a means to consume this content. So we're making HTTP requests to particular URLs in order to get the content. So we're using the HTTP client. We're using it asynchronously. And you can see here that it's a generic function, type T. So we can pass in the particular type that we're expecting to get for that particular URL. And we're serializing it back out into the same model. We're also going to be able to render it into the same object model that we had created on the other application, if you follow what I mean. So it just gives us consistency between the backend and the frontend in terms of the objects that we're rendering on the frontend. And of course, you could have multiple frontend applications that would have their own different type of implementation for, you know, that would know themselves how they're going to render the content that's been returned from the API. So we're building up. We're using a content URL builder to build up the URLs. So basically, if you want to get a particular page, then there might be a page filter entity is what you're using. So, you know, you'd have a page container ID or a page that's contained within that. You might have a language. You might have, I don't know, some other properties that kind of define where it is in the node hierarchy within Umbraco that the page lives. So in the frontend, we're instantiating the page filter entity. We're passing it into the constructor of this URL builder. And that URL builder then knows how to build the URL to make that HTTP request to the backend. And you can see here where we're adding the parameters in order to build up the URL. And then we've got just a single method that allows us to make the call to get the content. So you can see where we're instantiating the page filter entity, passing in page container ID, page ID, language, campaign name, number, and so on. So it just gives us complete flexibility in terms of the URL that corresponds on the Umbraco side of things, the URL that corresponds to that node that we're trying to retrieve the content for. Here you see an example of just a render page content method. You can see where we're instantiating an MVC model with our page content entity. And then that content page model is being passed through to a view. And within the view, we've got a mechanism for rendering our modules. The partial view property that you see here corresponds to the document type alias for the module that you've created in the back office. So it's the display templates. So this is probably where one of the more important parts of it, because this is where, based on the display templates, we're deciding exactly what model we want to pass to our view in order to render. So we're serializing the content property of the module container, which we saw earlier was being returned per module in that JSON for the page. And we're instantiating our model. So we've got a strong type model for the content that we want to render. And then that's being passed through to the appropriate view. So in the case of the generic module, it's being passed through to this view. And you can see that there's a method to render the module. And then again, you've got the hierarchy of rendering as it renders the rows, then renders the stacks, then renders the primitives that you might have within your stack, whether it's an image or a rich text or a product or a promotion or whatever the case might be. Okay, and Matthew mentioned about Web API. An example of Web API controller would be this module API controller. The reason why we adopted the approach using the Render MVC controller rather than using Web API is because with the Web API approach, you need to give a node ID, and the node ID is what gives you the context. Whereas if you use the Render MVC controller, you automatically get the context based on the URL that's requested. So Umbraco does that by default out of the box. So if you were to take this approach, you'd have to know, what is the ID of the node that I want to get the content for? Now we do use this sometimes, but we do need to know what the module ID is. That module ID corresponds to node ID. But it's the same idea then thereafter. We're using the type content passing in the ID. We're instantiating an LPM context, and then we're instantiating one of our providers passing the context and then returning out the content in JSON format out of that. So I think that covers the deep dive into the code. Let me see now. I think I probably covered everything. There's a few slides here just in terms of giving more of an illustration about what we're talking about. The template-based approach is the traditional approach when it comes to rendering Umbraco pages. So we're just an example of a page provider. We're instantiating a page provider passing in the context. We're accessing the content off that provider, and we're returning it in JSON format. And then on the front end, we're instantiating a URL builder, and we're retrieving the content asynchronously by making HTTP requests. And there's just an example screenshot of some of the outputs. And I think I've gone through most of this already. The base provider we saw in the LPM context, which gives us access to the content media, various other Umbraco API. We've got the module container provider, which determines which provider we need in order to get the relevant module. And that's where we use across all of the modules for the page. We saw a set of views that are used to build up those modules of content for each of the pages. And basically what we're doing is we're taking all of the... I suppose we're giving a common example. We already had some backend applications within the overall application suite. It's a complex e-commerce system. What we're doing is we're taking that e-commerce data, and we're enriching us with content. And by doing it that way, it just gives us the flexibility to enrich it with content while keeping a lot of that old legacy systems or even newer systems just abstracted away from the front end. So the front end is focused solely on just rendering the content. So we make requests to the WCF services, get me this product, get me this promotion. That comes back, and then we're adding extra content to those pages. So we're enriching the e-commerce data. This just gives you an example of the structure. So you might have a banner module across the top. You've got a mega navigation module for the site, hero module, generic module. So you're building up a collection of modules as you're rendering the site. And then you have its own entity and its own serialized JSON that we're using to return out of Mbrako and render on the front end. So I'm going to hand you back to Matti again now, and he's going to take you through some plugins and custom sections. And then we'll finish up then with discussing some of the applicability of the solution. So coming... We have a lot of plugins in... avon.com, Mbrako back office. Mostly all the plugins are really small. So just a couple of lines of code doing what that feature needs to be. And then we are trying to combine all the plugins to get the bigger... like the bigger data type that helps content authors to do what they need to do. So we're heavily relying on the web API for all the plugins. For the plugins, we took the approach... So we have the plugins folder in the solution. Then every plugin has its own folder named. And in every folder, there's a controller that... TS model, TS and the view. There's also a possibility of multiple views. And we have everything relating to a plugin is in a... .config file. So if you're building a new plugin, you don't need to... You don't need to create... Angular model and see how it's named. You put everything in the .config. And when the config is read, it populates your models and gets you the data you need. We also heavily rely on the package.manifest file. And it's really good idea to use it because you can use pre-values. And we have a heavily use of pre-values to just change the API. So if on the web API, the URL change, we can just change it on the developer section for the data type. And the data will be there. So... And we also use it for enable-disable options. But that's what we saw in the... We're not taking the approach we saw in the talk about... To add checkboxes. So we have... We have a configurable way to just add a new option to the pre-values. So this would be one of the example. So we have a product plugin. And then we use that plugin on three data types for the product lookup. Product lookup single variant and products multi-picker. All three are using the same plugin. But they have different option setup and different API URL. We also introduced the type ahead in the... If you heard of it in Umbraco for that plugin. And if you ever try to implement type ahead into Umbraco, you'll see that Umbraco has a lot of CSS classes that don't really work out with type ahead. So we need to do some inlining styles and import them at the end of all the styles to get it show the content. So the product lookup, that would be a data type. We chose the plugin called product picker. And then we use the pre-values. So how many products will the content author see when he or she starts to type the name of the product? How many products will be shown on the front end? So when the content author adds more than, I don't know, in this case 20 products, will only show 20 products. We also have an idea. So in avon.com, they have multiple campaigns. In every single moment, there's a number of three campaigns active. And we have in that product picker plugin, if the product is no longer in any of the campaigns, we don't show it on the mega nav, even though the content authors set that product to be shown. So they don't need to worry. And we'll show them in the back office also. So that it won't show at the front end. So they don't need to constantly update the products and see what's active and what's inactive. But that's possible because we took the headless approach. So it's relatively easy just to check if something's active or not. And then when we get the JSON back, it don't show it. In this case, we call this API URL, which is also configurable. So it just takes a string format. And whatever you pass in your code for that plugin. So in this example, we'll pass a node ID, which we need because we don't have the LPM context. We pass the search term. In this case, it will be product name or whatever. And then exclude variant products or how many products to take. So we can also just call the API with the search term and then return something else. So we're no longer just searching for the products. And we did have an update that also searches for the products, for the brands, and for the campaigns. But it's relatively easy. The developer implements the web API and just changes the URL. And the data is flowing back to the back office. In this case, we slightly modified. So we use number of allowed products 20 and number of show products 20. This is something we built. We have a lot of data types and document types in the Umbraco. So we have an Umbraco Explorer. That's how we called it. You can easily search by name for any of the data types or document types. When you click on any of the data types, you get how many times that data type is reused. So how many times you're using that plugin. And then when you click, you also get this information. So this is one screen. You also have when it was created, where it's reused. And then you have all the nodes where you're using that plugin, where the document type is. And you can drill down even further, clicking on the nodes. And you'll get the list of all the nodes in your tree. And then even further, you can click navigate. And it will open that node in a new tab. So it's easy for the content author to see where the plugin is. For the document types, we have the same approach. So you can filter all the document types available in Umbraco. You can get all the properties. So you get the tabs in the left side. And then in the right side, you get all the properties for that tab. So it's easy to see if something's got wrong. Or you just need to see where, for the developer, they just need to see where something is used to remember to just modify it. There's also a section called Tweaks. So the first couple of folders are plugins. So we have a scheduled publishing folder, switch toggle, transfer content, color picker, Umbraco Explorer, vanity URL builder. So those are all plugins. But we have a folder that needs to be called Tweaks. Tweaks allow you to modify Umbraco back office completely. And you can do whatever you want with it. You can change the login screen. You can change whatever. You can see that the Tweaks folder has a CSS and JS folder. Those folders are empty, but it needs, because of the convention, how Umbraco reads the data. And then you have a views. In this case, it has a media picker. We completely redid the panel that opens when you choose an image. So you don't need to go to the folders and drill down if you have a really big structure and choose the image. Everything is type ahead driven. So just start typing the name of the image or just paste the ID of the image and you'll get that image. So it's relatively fast for the content author to pick the image. There's also a controller TS and model TS and style less and Tweaks TS. We're also using our approach for that media picker to intercept whatever Umbraco back office is calling. So we have a functional function. called Tweaks Search for Media that uses the Angular IQ service. And there we have an IUmbraco interceptor that uses the Umbraco services IMediaPicker model. We did a pull request for the Umbraco TypeScript definitions. So you can easily, if you're using TypeScript, put it in your solution and have all the services, all the providers, Umbraco has in JavaScript strongly typed and get your IntelliSense. So you see that we hook into the Umbraco.Services module and then we pass a HTTP provider which allows us to plug into all the requests Umbraco is doing when somebody is going through the UI. And then when the request comes in, we just replace the MediaPicker with a custom panel. So we didn't touch any of the source code of Umbraco and we have a completely new panel. And it's easily extendable. I think there's a really good blog post about Tweaks folder in the 20 days of Umbraco, if I'm correct. And then we have custom sections. We have currently four custom sections. The custom sections are there for the developers and for the content authors. And I said at the beginning, when you're doing plugins for the custom section, everything is driven with the .config file. So you create a new plugin XML node. Then you put the name, the folder where the plugin lives, the name of your Angular controller. And then we also extend it further so you can use user roles and you can easily change who can see anything in the custom sections. And then, you know, the Umbraco tabs in the custom sections, you also create a new tab, give it the ID, give it the label that will show in the UI, give it the description, the template, which will render that tab. And then you have the template model, which is also an Angular property on your controller. So everything's here, and if something goes wrong, you know where to look. There's also... There's also... Yeah, that's the view of the LPM utilities. So you have the back office, and we have the archive maintenance, campaign maintenance, media maintenance, and preview maintenance. The media maintenance was relatively new implemented. It allows you... It has four tabs to see the content authors can filter just to see the folders created in the, let's say, last 10 days or to see everything's in a grid. You don't need to go into the Umbraco built-in media maintenance. You can use the new one, which is really faster for them to use. The archive maintenance is something we use to push the content daily. All the changes that the content authors do, we push them every morning to the production from staging. And it's using the DAC pack from Microsoft, and we build a dashboard for that. So you can choose your application model, you create your DAC pack, which is just a snapshot of the complete database, and then we use it as an archive. So every time a content author isn't sure or they want something archived, like a big campaign, they just create a DAC pack, and we allow them to do that. It's using SignalR on the back, so when somebody starts the archive maintenance, whoever goes, let's say another content author goes and logins and goes to the archive maintenance, they won't be able to do any archiving. Because one is already running, and it will tell them the progress. We hooked into the Umbraco events, and we have the complete log, not only our log, but the log of what the Umbraco database is doing underneath. And I'll give it back to Martin, just a summary. Thanks, Patrik. So we've gone through some of the kind of implementation of the headless approach. I suppose we just want to discuss a little bit about the applicability of the solution. I think Matt here already mentioned at one stage earlier on in the presentation that if you've got a smaller scale implementation of some information portal or something that you need to do, then maybe the headless approach is not the right approach, because there is going to be more development and more overhead in terms of maintaining a solution that is made up of two tiers, where you've got a front end and a back end. And that's where we're dealing with enterprise e-commerce customers with large platforms, potentially with already a REST services tier that's already available to us to consume to get the e-commerce data, or a WCF services tier in the case of Avon. Then it's better for us that we invest the time to set up a headless approach so that we can kind of decouple the content from the e-commerce data, decouple the content from the front end. It also gives us the flexibility from a deployment perspective. So we've got a separate deployment for continuous integration and deployment process for our Umbraco application and another one for our front end application. As well as that then in terms of server resources, you might find that the UI application needs more or less resources than the Umbraco application. So you've got the flexibility to deploy the two applications to a different set of servers and to allocate memory resources or whatever it is that you need. So that's what is needed depending on the performance of the applications. And there's also the support for different caching policies. So we use output caching heavily right across the solution suite. So we use output caching obviously in the UI, but we also use output caching in the Umbraco application. So we can set specific caching policies there to just again help to optimize the performance of the overall application suite. So I mentioned about the applicability in terms of large solutions. Maybe if you've got distributed systems, it might be useful to use a headless approach. If you've got pre-existing applications that you want to integrate CMS into, then you might not want to redevelop that application from the ground up on top of Umbraco. You might instead set up a separate Umbraco CMS and expose content from that in JSON format. And it just gives you the flexibility then to go into your pre-existing application and make changes just as the requirements arise without starting it from scratch. And basically the applicability then in terms of the headless approach is that you can reuse the content anywhere. So any particular channels or platforms where you want the content. So we already probably went through a lot of pros and cons. Some of the pros like the cross-platform publishing, the decoupled teams, the speed. Some of the cons then might be in terms of the tooling. So because you've got this separate application, then you mightn't be able to use it. You might be able to use the tooling in the same way as you would if you adopt the traditional approach. So one of those is in relation to the preview. So in the case of preview, we all know how that works from an Umbraco perspective. There's a preview cookie set and the content author can toggle to see the preview of that page and they will see the unpublished content as it would look after they publish it. So you don't get that as easily with the headless approach. So there's a bit of an overhead in terms of trying to find ways around that, which we also did for avon.com. Maybe that's the subject of another talk or something in the future. I won't go into it now. But basically it's a preview API controller that just supports that feature that is missing because we took the headless approach. So that's it for our presentation. So just to open up now to everybody if anybody has any questions for either of us. Sure. One second. Thank you. Because of the... Did you call it a module approach for the front end? Yeah. Because of that, how many HTTP requests does the client make per page? Only one. Okay. So yeah, it's just one for the page. There might be some other supporting requests, for example, to get header content or to get footer content maybe. But then they've got their own caching policies. So like where you go to the home page for the very first time, you're going to make or you might make... I think there might be four or five requests or something. HTTP requests will go from the front end to the back end. But the header is cached, the footer is cached. So as soon as you go to another page, you're achieving cached content for the header and footer, for example. That's a good approach. Can it do nested modules? Or would there be no reason for that? What we found with the modules we developed is this. It gives an awful lot of flexibility with the way they're set up. You can build up as many rows and stacks as you want right throughout the modules. So we haven't had a requirement for nested modules. But yeah, I mean, with a little bit of changes and refactoring, we could support it because what you're doing is you're saying, I've got any given primitive. My primitive is a rich text, an image, a product, a promotion, a video maybe. And we're putting that into the stack or putting a number of them into the stack depending on the requirements. Okay. What if we change that to allow you to put a module into a stack? Then you've got nested modules. Okay. Thank you. Yep. Thanks. Anyone else? You can pass it back, actually. I just wanted to ask about you mentioned caching. But invalidating the cache must be quite tricky. So in the sense of when you publish something on the back end, how does the kind of front end know that there's new content available? Okay. So the approach that we've taken for AVEN.com is a little bit different to what many people might be used to dealing with where you've got the publishing. As soon as you publish, the content is live, right? Which is probably what you're referring to. Whereas in our case, we've got a staging environment. And we run a nightly push to push that content to production. So when that nightly push runs like at 3 or 4 or 5 o'clock in the morning or whatever it is US time, then you've got your full content migrated to production. So the normal cache eviction is just going to kick in. So when somebody goes to browse the site later that morning US time, they've got all the new content. There is a challenge in terms of ad hoc content pushes through the day. So for ad hoc content pushes through the day, we use Courier. So Courier will allow you to push the content from staging to production. And it will, by default, evict any of the Umbraco caching. We try not to do it much because then the other caching is going to prevent the content from being refreshed. But there are various different things that we've built in that we wouldn't let our AVEN.com or our clients use but that we would use ourselves if one of those situations arose. But we try to avoid them because we try to rely on the natural invalidation of cache, letting it expire naturally because it's more kind of a best practice approach. And then, as I said, the content is pushed nightly. Yeah. Thanks. Anyone else? Sure. It's a short one just from my understanding. Sure. So the front end application, it runs, is it a regular MVC application? Yeah. So no Umbraco at all, right? No, exactly. That's it. So it's just a pure ASP.NET MVC solution. Yeah, okay. All right. That makes sense. Yeah. Thanks. So there's a question about the continuous integration process of the Umbraco. Yeah. Okay. So we use Octopus deploy. Within DCARS Systems Ireland, we have a number of different projects and we might be using different build mechanisms. We use TFS build for some projects. We use Jenkins build for other projects. But basically, you've got a process that builds the solutions. builds the solution. So we saw in Visual Studio earlier on, we saw all of the code files. So we've got a Visual Studio solution for that Mbrako application. All of the code files are checked in to TFS. And then Jenkins build kicks in and it actually builds an OctoPack. And the OctoPack is put onto an Octopus server. And Octopus server then has a number of configured environments. So in our case, for even a comm, we've got dev, QAF, QAP, staging and production. We have five environments, as well as obviously our local developer machines. So Octopus knows what it needs to do to deploy to each of those environments. And there's a thing referred to as a tentacle, which is basically just an Octopus client that sits in each of the servers. And it's a service running on the machines. So basically the Octopus, when you kick off a deployment, the Octopus server takes that OctoPack, pushes it up to the server, and then the tentacle kicks in and basically just, just unwraps or unzips, if you like, all of that package, get your binaries, all of your code files, and pushes them into the appropriate directory. So for Mbrako, we're using that deployment approach as well. Do you ever have any issues with, do you have poll calls for the document types? Like you write poll calls for those? Yeah, so every document type, we've got a corresponding entity. Yeah, do you ever have issues with those being out of sync with each other? Or is the database always updated? It's purely up to the... It's up to the developer to make sure that they're kept in sync. So because all of those entities are source controlled, if a developer has particular requirements to change one of those document types, it's their responsibility to make sure to update that entity. Oh yeah, and what we do is, like, we're developing locally. Mattia went through the DACPAC stuff and mentioned that. What we do is we push the content to all of the other environments as well. So staging is our master for content, including document types, including all of the schema, if you like, for your... Yeah. Yeah. So that's pushed down to dev, QF, QAP on a nightly basis as well, which means that we can be 100% sure that when we're developing in dev, we're dealing with real live content. We're dealing with real live on bracket changes. So if we wanted to create a new document type, we do it locally. We do our development locally. We test locally. And at that point, we would make that document type on staging. And then the nightly process will kick in and push it to the other environments. So what happens when a developer does a new clone to a... to clean machine? Do they need... They obviously need to get the database packaged separately. Yeah. Yeah. So they can just DAC packet down to their machine if they need to. In fact, to set up the demo today, I had to use the feature that Matthew developed around DAC pack to pull the content onto my machine because I didn't want to rely on having a data connection or anything, you know, back to our offices in Cork. And you can easily request the staging data, whatever, on your local machine. You don't rely on a daily push like you do for the production. You can easily request from any environment you can push to your local environment. Okay. Thank you very much. Anyone else? No? Okay. Thank you. Great. Thanks very much, guys. Thank you. Thank you. Thank you. Thank you.