Headless CMS 的内部

1136 篇文章 56 订阅
635 篇文章 16 订阅

目录

介绍

Headless CMS

什么是Headless CMS?

Headless CMS的优点

Headless CMS解决方案的局限性

使用HCMS的缺点

HCMS的局限性

何时何地使用Headless CMS?

RawCMS:构建自己的Headless CMS

为什么另一个Headless CMS?

RawCms特征选择

架构

服务层

认证

Lambda表达式

使用lambda添加自定义端点

验证数据

更改保存数据

插件

如何使用RawCMS

从Docker安装

从Zip Release安装

建立你自己的

兴趣点


介绍

在本文中,我们将了解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 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值