Headless CMS 的介绍

2019-09-17 17:20:59

参考地址 Headless CMS 的内部

介绍

在本文中,我们将了解Headless CMS,我们将了解它的优点以及何时使用方便。此外,我们将列举实际的主要限制。为了更好地理解HCMS如何在幕后工作,我将解释如何设计和构建RawCMS,一个带有Oauth2Aspnet.Core Headless CMS,扩展插件系统,业务逻辑支持。该解决方案可在GitHub上获得,并作为演示版在docker hub上发布。

Headless CMS

什么是Headless CMS

传统的CMS结合了内容和渲染部分,同时,Headless CMS仅关注内容。这似乎是一种限制,因为勉强说,你失去了一些东西。HCMS的目的是将逻辑与内容分离,从而实现简单的变更管理,并在许多组件中分解复杂的应用程序,每个组件都有其单一的责任。

朝着这个方向前进,HCMS可以取代实际上你正在调用的后端,并节省了许多创建CRUD语句的有用工作。

HCMS诞生于创建多组件应用程序,您可以快速更改表示逻辑和设计,这是一个很大的改进,当您在现代网站或应用程序上工作时,由于业务需求,您需要每年更换一次UI 

许多供应商出售他们的产品并将其标记为“HCMS”仅仅是因为它是分离的(并且因为它听起来很酷并且可能推动销售改进)。在我看来,我与原始的整体定义有着严格的联系:Headless cms意味着API首先是非单片CMS,完全与接口或其他组件分离。

Headless CMS的优点

为什么要使用Headless CMS?我可以简单地说,在某些情况下,解耦系统,更容易更换前端并加快开发阶段是有用的,但我觉得有必要使用无序列表更好地解释。

  • 全渠道准备:在Headless CMS中创建的内容是纯粹的,您可以在您想要的每个上下文中使用。如果您在其上存储了一些新闻内容,您也可以在公共网站或内部网上发布,将数据输入到一个地方。

  • 低运营成本Headless CMS是产品,所以,一旦你选择了一个好的产品,我预计它将是即插即用的。此外,与自定义解决方案相比,更新和错误修复来自供应商的免费提供。

  • 缩短产品上市时间Headless CMS促进了敏捷的工作方式。您可以让多个团队参与后端和前端,这样可以减少时间。此外,由于HCMS区域是API消耗的数据存储的垂直解决方案,大部分事情已经完成,因此您必须专注于数据设计而不是技术细节(例如浪费时间考虑有效载荷,何时可以免费的使用OdataGrahql)。

  • 垂直解决方案HCMS做一件事。这使得学习和维护变得非常容易。

  • 灵活性:一旦你选择了你的HCMS(无论是本地还是云端),你的开发人员都可以使用他们喜欢的任何语言来实现前端。这意味着您可以自由地使用技术限制。

Headless CMS解决方案的局限性

与传统的CMS相比,HCMS相当年轻,因此,即使很多产品在过去几年诞生,大多数产品也不是那么成熟,无法完全取代传统的API后端。在这个阶段中,我将分享我对我发现的限制的经验。功能可能会因特定产品而异,如果是本地或saas解决方案。

实际上,主要有两种CMS Headless限制:

  • 使用HCMS的缺点

  • 您安装的产品的限制

使用HCMS的缺点

HCMS需要雇佣多个团队来实现工作并行化的好处。此外,由于HCMS没有任何渲染,所有的表示逻辑都被要求提供给客户端。这对于解耦很有用,但在所有情况下,您只有一个消费者解耦优势并不那么相关,并且您在数据获取过程中引入了更多的复杂性和延迟。另一个问题是关于业务逻辑。在哪里实施?如果你不想实现HCMS,你必须把它放到表示层,并且有多个消费者,当逻辑存在于多个地方时,您将复制它,陷入问题中。否则,尝试将其放入HMS,您会发现大多数云解决方案\产品都不那么灵活。这引入了下一个主题,所有HCMS的限制是什么?

HCMS的局限性

测试最重要的HCMS解决方案,我遇到了许多困难的情况,以下是最常见的限制列表。考虑到这取决于产品,有人可能有或没有,但一般来说,大多数都很常见。

  • 针对外部提供程序的身份验证:大多数解决方案不允许针对外部系统对用户进行身份验。我说的是最常见的情况,即您拥有一个中央身份验证系统,并且所有各方都会传递用户令牌\票证以代表用户进行操作。换句话说,如果我有一个oauth2服务器,我想在前端进行身份验证,并使用令牌向内部网的所有应用程序进行调用,而不仅仅是HCMS,并被识别为我自己。

  • 非标准输出格式:有些使用graphqlOdata,这很好,因为它为数据消耗提供了标准方法。问题是某些并不意味着全部,所以你必须注意选择你的HCMS

  • 业务逻辑:在大多数情况下,不可能在运行时定义业务逻辑,在某些情况下也不可能扩展核心应用程序。

  • 可扩展性:很难找到一个解决方案,您可以编写自己的代码并更改业务逻辑或添加额外的东西。部分原因是许多供应商将其HCMS设计为哑数据存储,部分原因是管理可扩展性的复杂性。

何时何地使用Headless CMS

Headless CMS是一个很好的机会,但在这里,我们必须了解使用它来优化成本/效益比的最佳方案。问题在于,使用常规HCMS,定制非常有限,因此如果您不在正确的情况下,很难将HCMS混合以实现业务需求。而且,像裸数据存储一样使用它会使它变得毫无意义。

何时使用HCMS很方便:

  • 在一段时间里,UI上有很多变化

  • 许多共享相同信息的应用程序和一个管理它的团队

  • 您对数据的业务逻辑很少

  • 你可以聘请多个团队(be + fe

您何时不应该使用HCMS

  • 有一个符合您需求的垂直解决方案(例如,您希望博客使用wordpress

  • 你有很多业务逻辑

  • 你不是数据的主人

RawCMS:构建自己的Headless CMS

在本章中,我们将看到RawCMS是什么以及我如何使用ASP.NET CoremongodbDocker和一些幻想创建Headless CMS

为什么另一个Headless CMS

RawCMS的目的是在没有HCMS的共同限制的情况下生成HCMS......以及在新技术上训练有趣的东西;-)

RawCms特征选择

所以我们将提出的功能:

  • 可以使用oauth2自省(或内置的auth系统)对其他auth系统进行身份验证的可能性

  • 可以使用挂钩/事件系统添加业务逻辑的可能性

  • 可以添加自定义端点来管理与数据无关的事件的可能性

  • 可以在插件系统中添加功能的可能性

  • 验证数据的可能性

  • 使用多种协议公开数据,如webapiGraphQLOdata

架构

基本上,我将实现的架构如下。实际上,插件部分有一些限制,缺少工作流管理,但其他部分功能齐全。

服务层

服务层是系统的核心部分。使用mongodb实体上的常规JObject映射,您可以在mongo集合中存储您想要的任何内容,所有数据都是无类型的。

这是本类中最相关的部分,用于解释它的工作原理。

public class CRUDService {public JObject Get(string collection, string id){//Create filter by id (all entity MUST have an id field, called _id by convention)FilterDefinition<BsonDocument> filter = Builders<BsonDocument>.Filter.Eq("_id", BsonObjectId.Create(id));IFindFluent<BsonDocument, BsonDocument> results = _mongoService.GetCollection<BsonDocument>(collection).Find<BsonDocument>(filter);return ConvertBsonToJson(json);}public ItemList Query(string collection, DataQuery query){FilterDefinition<BsonDocument> filter = FilterDefinition<BsonDocument>.Empty;if (query.RawQuery != null){filter = new JsonFilterDefinition<BsonDocument>(query.RawQuery);}InvokeAlterQuery(collection, filter);IFindFluent<BsonDocument, BsonDocument> results = _mongoService.GetCollection<BsonDocument>(collection).Find<BsonDocument>(filter).Skip((query.PageNumber - 1) * query.PageSize).Limit(query.PageSize);long count = Count(collection, filter);return new ConverToItemList(results, (int)count, query.PageNumber, query.PageSize);}public JObject Update(string collection, JObject item, bool replace){//Invoke validation eventsInvokeValidation(item, collection);// create collection if not existsEnsureCollection(collection);FilterDefinition<BsonDocument> filter = Builders<BsonDocument>.Filter.Eq("_id", BsonObjectId.Create(item["_id"].Value<string>()));//Invoke presave eventsInvokeProcess(collection, ref item, SavePipelineStage.PreSave);//insert id (mandatory)BsonDocument doc = BsonDocument.Parse(item.ToString());doc["_id"] = BsonObjectId.Create(item["_id"].Value<string>());//set into "incremental" update modedoc = new BsonDocument("$set", doc);UpdateOptions o = new UpdateOptions(){IsUpsert = true,BypassDocumentValidation = true};if (replace){_mongoService.GetCollection<BsonDocument>(collection).ReplaceOne(filter, doc, o);}else{BsonDocument dbset = new BsonDocument("$set", doc);_mongoService.GetCollection<BsonDocument>(collection).UpdateOne(filter, dbset, o);}//Post save eventsInvokeProcess(collection, ref item, SavePipelineStage.PostSave);return JObject.Parse(item.ToJson(js));}}

认证

认证部分完成添加身份服务器并使用基于RawCms设置的不同配置。通过这种方式,我们可以使用内部身份服务器(其他人获取我们的令牌,我们拥有用户数据)或与其他认证系统集成(我们在请求标头中获取令牌,我们够能将其推送到其他oauth系统上)。

这是代码中最相关的部分。此代码在身份验证插件启动期间调用,并从数据库获取配置。与该类的认证配置无关的所有代码部分都被省略。

public override void ConfigureServices(IServiceCollection services){base.ConfigureServices(services);//configuration came from constructorservices.Configure<ConfigurationOptions>(configuration);services.AddSingleton<IUserStore<IdentityUser>>(x => { return userStore; });//... registering all identity server services for user and roles (all code omitted)services.AddSingleton<IUserClaimsPrincipalFactory<IdentityUser>, RawClaimsFactory>();// configure identity server with in-memory stores, keys, clients and scopesservices.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryPersistedGrants().AddInMemoryIdentityResources(config.GetIdentityResources()).AddInMemoryApiResources(config.GetApiResources()).AddInMemoryClients(config.GetClients()).AddAspNetIdentity<IdentityUser>().AddProfileServiceCustom(userStore);if (config.Mode == OAuthMode.External){OAuth2IntrospectionOptions options = new OAuth2IntrospectionOptions{//... set option basing on config (code omitted)            };options.Validate();services.AddAuthentication(OAuth2IntrospectionDefaults.AuthenticationScheme).AddOAuth2Introspection(x =>{x = options;});}else{services.AddAuthentication(OAuth2IntrospectionDefaults.AuthenticationScheme).AddIdentityServerAuthentication("Bearer", options =>{//... set option basing on config (code omitted)});}services.AddMvc(options =>{//this apply custom authentication like apitoken other than oauth standardoptions.Filters.Add(new RawAuthorizationAttribute(config.ApiKey, config.AdminApiKey));});}

Lambda表达式

Lamba是一个简单的命令模式实现,该名称的灵感来自无服务器模型,您可以将函数公开为rest端点。基于此,您可以通过实现lamba来调整系统中的所有内容。每个lambda实例都在运行时发现,并根据lamba类型和事件调用,并将数据上下文传递给它。

下面给出一些lambda示例。

使用lambda添加自定义端点

public class DummyRest : RestLambda{public override string Name => "DummyRest";public override string Description => "I'm a dumb dummy request";public override JObject Rest(JObject input){JObject result = new JObject(){{ "input",input},{ "now",DateTime.Now},};return result;}}

验证数据

public class MyCustomValidation : SchemaValidationLambda{public override string Name => "My custom Validation";public override string Description => "Provide  entity validation";public override List<Error> Validate(JObject input, string collection){//do here all check with datareturn ImplementCheckHere(input, collection);}}

更改保存数据

public class AuditLambda : PreSaveLambda{public override string Name => "AuditLambda";public override string Description => "Add audit settings";public override void Execute(string collection, ref JObject Item){if (!Item.ContainsKey("_id") || string.IsNullOrEmpty(Item["_id"].ToString())){Item["_createdon"] = DateTime.Now;}Item["_modifiedon"] = DateTime.Now;}}

插件

插件系统背后的想法是创建一个项目,开发您的功能,将DLL扔进bin文件夹并使其可用于应用程序。其中的主要部分将被讨论成一篇专门的文章,因为解释和偏离主题需要很长时间。我只想在这里展示一下插件系统的原理。这也意味着您可以使用nuget作为交付系统或功能市场。

public class GraphQLPlugin : RawCMS.Library.Core.Extension.Plugin{public override string Name => "GraphQL";public override string Description => "Add GraphQL CMS capabilities";public override void Init(){Logger.LogInformation("GraphQL plugin loaded");}public override void ConfigureServices(IServiceCollection services){//will be triggered on Startup.cs ConfigureServicesbase.ConfigureServices(services);}private void SetConfiguration(Plugin plugin, CRUDService crudService){//used to receive configuration from system}public override void Configure(IApplicationBuilder app, AppEngine appEngine){// will be triggered on Startup.cs Configurebase.Configure(app, appEngine);}}

如何使用RawCMS

为了让用户测试这个解决方案,我实现了很多选项。

Docker安装

这是最方便的。您可以在文档内找到一个docker compose示例,或者您可以使用docker run然后链接到mongodb实例。

docker run rawcms -p 80:8081

或使用docker compose

version: '3'services:rawcms:build: .ports:- "54321:54321"   links:- mongoenvironment:- MongoSettings__ConnectionString=mongodb://mongo:27017/rawCms- PORT=54321- ASPNETCORE_ENVIRONMENT=Dockermongo:image: mongo

环境变量MongoSettings__ConnectionString用于将连接字符串传递给应用程序。

Zip Release安装

如果您尚未准备好容器,可以从GitHub版本下载zip文件,并将其作为常规ASP.NET Core应用程序手动部署。

建立你自己的

第三种可能性是分解解决方案,并在本地发挥作用。目前,您的设置中没有任何nuget包,因此建议的最佳解决方案是将github repo添加为子模块或子树。

兴趣点

HMCS是解耦架构和避免无用工作的绝佳机会。这可能会带来诸如减少时间和成本等好处,使各方独立。当然,这不是灵丹妙药,您必须了解垂直解决方案是否更方便,或者您的企业登录是否避免你使用它。

我试图实现HCMS,我们看到了一个非常重要的话题。这很有趣,我们了解如何实现最重要的主题,以超越HCMS的实际技术限制。


  • 2017-12-05 22:30:02

    php7.0升级php7.2

    看电脑上的教程要备份7.0配置文件以及扩展啥的,我感觉不如卸载干净重新安装

  • 2017-12-06 09:35:10

    分页优化的四种方式

    在大数据量的情况下,原本很简单的分页如果没有处理好,你会发现分页的请求会消耗你大量的数据库时间。如果你遇到了这个问题,文章给了你几个很好的解决的方案。当然,初学者若能看完这篇文章,那么它会指导你写出更具有扩展性的分页代码。