目录
介绍
在本文中,我们将了解Headless CMS,我们将了解它的优点以及何时使用方便。此外,我们将列举实际的主要限制。为了更好地理解HCMS如何在幕后工作,我将解释如何设计和构建RawCMS,一个带有Oauth2的Aspnet.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消耗的数据存储的垂直解决方案,大部分事情已经完成,因此您必须专注于数据设计而不是技术细节(例如浪费时间考虑有效载荷,何时可以免费的使用Odata或Grahql)。
- 垂直解决方案:HCMS做一件事。这使得学习和维护变得非常容易。
- 灵活性:一旦你选择了你的HCMS(无论是本地还是云端),你的开发人员都可以使用他们喜欢的任何语言来实现前端。这意味着您可以自由地使用技术限制。
Headless CMS解决方案的局限性
与传统的CMS相比,HCMS相当年轻,因此,即使很多产品在过去几年诞生,大多数产品也不是那么成熟,无法完全取代传统的API后端。在这个阶段中,我将分享我对我发现的限制的经验。功能可能会因特定产品而异,如果是本地或saas解决方案。
实际上,主要有两种CMS Headless限制:
- 使用HCMS的缺点
- 您安装的产品的限制
使用HCMS的缺点
HCMS需要雇佣多个团队来实现工作并行化的好处。此外,由于HCMS没有任何渲染,所有的表示逻辑都被要求提供给客户端。这对于解耦很有用,但在所有情况下,您只有一个消费者解耦优势并不那么相关,并且您在数据获取过程中引入了更多的复杂性和延迟。另一个问题是关于业务逻辑。在哪里实施?如果你不想实现HCMS,你必须把它放到表示层,并且有多个消费者,当逻辑存在于多个地方时,您将复制它,陷入问题中。否则,尝试将其放入HMS,您会发现大多数云解决方案\产品都不那么灵活。这引入了下一个主题,所有HCMS的限制是什么?
HCMS的局限性
测试最重要的HCMS解决方案,我遇到了许多困难的情况,以下是最常见的限制列表。考虑到这取决于产品,有人可能有或没有,但一般来说,大多数都很常见。
- 针对外部提供程序的身份验证:大多数解决方案不允许针对外部系统对用户进行身份验。我说的是最常见的情况,即您拥有一个中央身份验证系统,并且所有各方都会传递用户令牌\票证以代表用户进行操作。换句话说,如果我有一个oauth2服务器,我想在前端进行身份验证,并使用令牌向内部网的所有应用程序进行调用,而不仅仅是HCMS,并被识别为我自己。
- 非标准输出格式:有些使用graphql或Odata,这很好,因为它为数据消耗提供了标准方法。问题是“某些”并不意味着“全部”,所以你必须注意选择你的HCMS。
- 业务逻辑:在大多数情况下,不可能在运行时定义业务逻辑,在某些情况下也不可能扩展核心应用程序。
- 可扩展性:很难找到一个解决方案,您可以编写自己的代码并更改业务逻辑或添加额外的东西。部分原因是许多供应商将其HCMS设计为哑数据存储,部分原因是管理可扩展性的复杂性。
何时何地使用Headless CMS?
Headless CMS是一个很好的机会,但在这里,我们必须了解使用它来优化成本/效益比的最佳方案。问题在于,使用常规HCMS,定制非常有限,因此如果您不在正确的情况下,很难将HCMS混合以实现业务需求。而且,像裸数据存储一样使用它会使它变得毫无意义。
何时使用HCMS很方便:
- 在一段时间里,UI上有很多变化
- 许多共享相同信息的应用程序和一个管理它的团队
- 您对数据的业务逻辑很少
- 你可以聘请多个团队(be + fe)
您何时不应该使用HCMS:
- 有一个符合您需求的垂直解决方案(例如,您希望博客使用wordpress)
- 你有很多业务逻辑
- 你不是数据的主人
RawCMS:构建自己的Headless CMS
在本章中,我们将看到RawCMS是什么以及我如何使用ASP.NET Core,mongodb,Docker和一些幻想创建Headless CMS。
为什么另一个Headless CMS?
RawCMS的目的是在没有HCMS的共同限制的情况下生成HCMS(......以及在新技术上训练有趣的东西;-))
RawCms特征选择
所以我们将提出的功能:
- 可以使用oauth2自省(或内置的auth系统)对其他auth系统进行身份验证的可能性
- 可以使用挂钩/事件系统添加业务逻辑的可能性
- 可以添加自定义端点来管理与数据无关的事件的可能性
- 可以在插件系统中添加功能的可能性
- 验证数据的可能性
- 使用多种协议公开数据,如webapi,GraphQL,Odata
架构
基本上,我将实现的架构如下。实际上,插件部分有一些限制,缺少工作流管理,但其他部分功能齐全。
服务层
服务层是系统的核心部分。使用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 events
InvokeValidation(item, collection);
// create collection if not exists
EnsureCollection(collection);
FilterDefinition<BsonDocument> filter = Builders<BsonDocument>.Filter.Eq
("_id", BsonObjectId.Create(item["_id"].Value<string>()));
//Invoke presave events
InvokeProcess(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 mode
doc = 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 events
InvokeProcess(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 constructor
services.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 scopes
services.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 standard
options.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 data
return 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 ConfigureServices
base.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 Configure
base.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:
- mongo
environment:
- MongoSettings__ConnectionString=mongodb://mongo:27017/rawCms
- PORT=54321
- ASPNETCORE_ENVIRONMENT=Docker
mongo:
image: mongo
环境变量MongoSettings__ConnectionString用于将连接字符串传递给应用程序。
从Zip Release安装
如果您尚未准备好容器,可以从GitHub版本下载zip文件,并将其作为常规ASP.NET Core应用程序手动部署。
建立你自己的
第三种可能性是分解解决方案,并在本地发挥作用。目前,您的设置中没有任何nuget包,因此建议的最佳解决方案是将github repo添加为子模块或子树。
兴趣点
HMCS是解耦架构和避免无用工作的绝佳机会。这可能会带来诸如减少时间和成本等好处,使各方独立。当然,这不是灵丹妙药,您必须了解垂直解决方案是否更方便,或者您的企业登录是否避免你使用它。
我试图实现HCMS,我们看到了一个非常重要的话题。这很有趣,我们了解如何实现最重要的主题,以超越HCMS的实际技术限制。
原文地址:https://www.codeproject.com/Articles/1278159/Inside-Headless-CMS