业务逻辑层缓存应该设计

        在业务制定的时候很少会切入缓存设计这一环节,毕竟在指标不明确的情况这属于一种过渡设计.毕竟缓存切入有很多手段,在很多时候直接在WEB进行一个页面缓存就有着非常高收益的效果.缓存是一种横向的数据处理应用,一般在设计中引入AOP,ICO的应用组件都可以在后期切入添加.但AOP,ICO在没有比较丰富的经验情况引入会直接增加应用的复杂度和风险.在设计主要介绍一种简单的设计方式在设计阶段引用缓存但又不带来复杂的工作成本.

一个简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BlogService:Interfaces.IBlogService {
        public IList<Blog> List(string category, int size, int index, out int pages)
        {
            Expression exp = new Expression();
            if (!string.IsNullOrEmpty(category))
                exp &= Blog.iD == BlogLinkCategory.blog[BlogLinkCategory.category == category];
            int count = exp.Count<Blog>();
            pages = count / size;
            if (count % size > 0)
                pages++;
 
            return exp.List<Blog>(new Region(index, size), Blog.createTime.Desc);
        }
 
        public IList<BlogCategory> ListCategories()
        {
            return new Expression().List<BlogCategory>();
        }
 
        public Blog Get(string id)
        {
            return (Blog.iD == id).ListFirst<Blog>();
        }
}

        以上是一个完全不考虑缓存应用的情况,一般在前期都这样做,毕竟完成功能是首要面对的问题.

简单加入缓存

以List方法为例加入缓存处理方式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public IList<Blog> List(string category, int size, int index, out int pages)
{
 
    IList<Blog> result = redis.Get<IList<Blog>>("key");
    if (result == null)
    {
        Expression exp = new Expression();
        if (!string.IsNullOrEmpty(category))
            exp &= Blog.iD == BlogLinkCategory.blog[BlogLinkCategory.category == category];
        int count = exp.Count<Blog>();
        pages = count / size;
        if (count % size > 0)
            pages++;
 
        result = exp.List<Blog>(new Region(index, size), Blog.createTime.Desc);
        redis.Set("key", result);
    }
    return result;
}

这一平时在开发中看到比较的方式,说实话一开始考虑这样做的确是增加比较大的工作量,特别在前期阶段没有性能要求的情况,这只会增长工作和延时进度.还有就是缓存产品的偶合性也强达不到比较好的秀明度;毕竟开发人员还要学习具体缓存产品的API.

缓存实现的解偶

         可以在设计的时候制定一个简单的缓存储访问接口.

1
2
3
4
5
public interface ICached
   {
       T Get<T>(string key);
       void Set(string key, object data);
   }

          在设计的时候引入到接口定义中

 

1
2
3
4
5
6
7
8
9
10
11
12
public interface IBlogService
 {
     IList<Blog> List(string category, int size, int index, out int pages);
 
     Blog Get(string id);
 
     Blog Save(string id, string title, string keywords, string data, params string[] categories);
 
     IList<BlogCategory> ListCategories();
 
     ICached Cached { get; set; }
}

            这样一个简单针对逻辑层的Cached应用规范就出来了.这样可以大大降低了开发人员对缓存产品依赖;当然这个接口设计的简陋,在设计时有可能需要考虑超时设置等等.虽然解偶的目的达到了,但使用方便性上还是比较麻烦,工作并没有多大的减少.

缓存应用简化

        其实在接口针对Get定义一些简单的委托参数可以简单Cache在应用时的复发度.

1
2
3
4
5
public interface ICached
   {
       T Get<T>(Func<T> handler, string key);
       void Set(string key, object data);
   }

          那在编写逻辑的时候就比较简单一些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
    public IList<Blog> List(string category, int size, int index, out int pages)
    {
        dynamic eo = Cached.Get<dynamic>(() => {
            dynamic result = new ExpandoObject();
            Expression exp = new Expression();
            if (!string.IsNullOrEmpty(category))
                exp &= Blog.iD == BlogLinkCategory.blog[BlogLinkCategory.category == category];
            int count = exp.Count<Blog>();
            result.Pages = count / size;
            if (count % size > 0)
                result.Pages++;
 
            result.Blogs = exp.List<Blog>(new Region(index, size), Blog.createTime.Desc);
            return result;
        }, key);
 
        pages = eo.Pages;
        return eo.Blogs;
    }

        其实原理比较简单,就是Get内部实现如果相关key不存在的情况直接执行Func<T>的方法得到数据,并设置到缓存中.以上需要处理一些条件和分页返回所以感觉会复杂一些.一般代码下都简单.

1
2
3
4
5
public TextBlock GetByTitle(string title)
       {
           return Cached.Get<TextBlock>(() => (TextBlock.iD == title).ListFirst<TextBlock>(),title);
 
       }

        如果再花点小心思,那可以这样子

1
2
3
4
5
6
7
8
9
10
11
12
13
public User Get(string name)
      {
 
              return (User.name == name).ListFirst<User>();
      }
 
public User Get(string name)
      {
          return Cached[name].Get<User>(() =>
          {
              return (User.name == name).ListFirst<User>();
          });
      }

        这样基于在外面套一层Cached应用代码,里面的代码是不用修改.

总结 

        通过这样的设计在逻辑层或其他层面引用缓存设计并不会对整个代码带来多大的复杂度变化,刚开始完全可以引用一个完全都不做的ICached实现.在后期有需要的话直接更换代码.文章里的ICached的设计比较简陋毕竟只是用于体现这种设计模式而已.如果业务场复杂的情况这个ICached所反映的行为参数可能会复杂一些.不过开发人员总会有办法把复杂的调用变成简单的.

        

posted @   beetlex  阅读(3684)  评论(10编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述
点击右上角即可分享
微信分享提示