zoukankan      html  css  js  c++  java
  • 《ASP.NET Core 微服务实战》-- 读书笔记(第5章)

    第 5 章 创建数据服务

    选择一种数据存储

    由于我坚持要尽可能的跨平台,所以我决定选用 Postgres,而不用 SQL Server 以照顾 Linux 或 Mac 电脑的读者

    构建 Postgres 仓储

    在本节,我们要升级位置服务让它使用 Postgres

    为了完成这一过程,需要创建一个新的仓储实现,以封装 PostgreSQL 的客户端通信

    回顾一下位置仓库的接口

    public interface ILocationRecordRepository {
        LocationRecord Add(LocationRecord locationRecord);    
        LocationRecord Update(LocationRecord locationRecord);
        LocationRecord Get(Guid memberId, Guid recordId);
        LocationRecord Delete(Guid memberId, Guid recordId);
        LocationRecord GetLatestForMember(Guid memberId);
        ICollection<LocationRecord> AllForMember(Guid memberId);
    }
    

    接下来要做的就是创建一个数据库上下文

    数据库上下文的使用方式是创建与特定模型相关的类型,并从数据库上下文继承

    由于与位置数据打交道,所以要创建一个 LocationDbContext 类

    using Microsoft.EntityFrameworkCore;
    using StatlerWaldorfCorp.LocationService.Models;
    using Npgsql.EntityFrameworkCore.PostgreSQL;
    
    namespace StatlerWaldorfCorp.LocationService.Persistence
    {
        public class LocationDbContext : DbContext
        {
            public LocationDbContext(DbContextOptions<LocationDbContext> options) :base(options)
            {            
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
                modelBuilder.HasPostgresExtension("uuid-ossp");
            }
    
            public DbSet<LocationRecord> LocationRecords {get; set;}
        }
    }
    

    实现位置记录仓储接口

    // using Microsoft.EntityFrameworkCore;
    
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using StatlerWaldorfCorp.LocationService.Models;
    using Microsoft.EntityFrameworkCore;
    
    namespace StatlerWaldorfCorp.LocationService.Persistence
    {
        public class LocationRecordRepository : ILocationRecordRepository
        {
            private LocationDbContext context;
    
            public LocationRecordRepository(LocationDbContext context)
            {
                this.context = context;
            }
    
            public LocationRecord Add(LocationRecord locationRecord)
            {
                this.context.Add(locationRecord);
                this.context.SaveChanges();
                return locationRecord;
            }
    
            public LocationRecord Update(LocationRecord locationRecord)
            {
                this.context.Entry(locationRecord).State = EntityState.Modified;
                this.context.SaveChanges();
                return locationRecord;
            }
    
            public LocationRecord Get(Guid memberId, Guid recordId)
            {
                return this.context.LocationRecords.FirstOrDefault(lr => lr.MemberID == memberId && lr.ID == recordId);
            }
    
            public LocationRecord Delete(Guid memberId, Guid recordId)
            {
                LocationRecord locationRecord = this.Get(memberId, recordId);
                this.context.Remove(locationRecord);
                this.context.SaveChanges();
                return locationRecord;
            }
    
            public LocationRecord GetLatestForMember(Guid memberId)
            {
                LocationRecord locationRecord = this.context.LocationRecords.
                    Where(lr => lr.MemberID == memberId).
                    OrderBy(lr => lr.Timestamp).
                    Last();
                return locationRecord;
            }
    
            public ICollection<LocationRecord> AllForMember(Guid memberId)
            {
                return this.context.LocationRecords.
                    Where(lr => lr.MemberID == memberId).
                    OrderBy(lr => lr.Timestamp).
                    ToList();
            }
        }
    }
    

    为了实现以注入的方式获取 Postgres 数据库上下文,需要在 Startup 类的 ConfigureServices 方法里把仓储添加到依赖注入系统

    public void ConfigureServices(IServiceCollection services)
    {                                    
        services.AddEntityFrameworkNpgsql().AddDbContext<LocationDbContext>(options =>
            options.UseNpgsql(Configuration));
        services.AddScoped<ILocationRecordRepository, LocationRecordRepository>();    
        services.AddMvc();
    }
    

    数据库是一种后端服务

    在本例中,我们准备用环境变量来覆盖由配置文件提供的默认配置

    appsettings.json

    {
        "transient": false,
        "postgres": {
            "cstr": "Host=localhost;Port=5432;Database=locationservice;Username=integrator;Password=inteword"
        }
    }
    

    前面实现的仓储需要一种数据库上下文才能运作,为了给位置模型创建数据库上下文,只需要创建一个类,并从 DbContext 继承

    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Infrastructure;
    using StatlerWaldorfCorp.LocationService.Models;
    using Npgsql.EntityFrameworkCore.PostgreSQL;
    
    namespace StatlerWaldorfCorp.LocationService.Persistence
    {
        public class LocationDbContext : DbContext
        {
            public LocationDbContext(DbContextOptions<LocationDbContext> options) :base(options)
            {            
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
                modelBuilder.HasPostgresExtension("uuid-ossp");
            }
    
            public DbSet<LocationRecord> LocationRecords {get; set;}
        }
    
        public class LocationDbContextFactory : IDbContextFactory<LocationDbContext>
        {
            public LocationDbContext Create(DbContextFactoryOptions options)
            {
                var optionsBuilder = new DbContextOptionsBuilder<LocationDbContext>();
                var connectionString = Startup.Configuration.GetSection("postgres:cstr").Value;
                optionsBuilder.UseNpgsql(connectionString);
    
                return new LocationDbContext(optionsBuilder.Options);
            }
        }
    }
    

    创建了新的数据库上下文后,需要让它在依赖注入中可用,这样位置仓储才能使用它

    public void ConfigureServices(IServiceCollection services)
    {                                    
        //var transient = Boolean.Parse(Configuration.GetSection("transient").Value);
        var transient = true;
        if (Configuration.GetSection("transient") != null) {
            transient = Boolean.Parse(Configuration.GetSection("transient").Value);
        }
        if (transient) {
            logger.LogInformation("Using transient location record repository.");
            services.AddScoped<ILocationRecordRepository, InMemoryLocationRecordRepository>();
        } else {                
            var connectionString = Configuration.GetSection("postgres:cstr").Value;                        
            services.AddEntityFrameworkNpgsql().AddDbContext<LocationDbContext>(options =>
                options.UseNpgsql(connectionString));
            logger.LogInformation("Using '{0}' for DB connection string.", connectionString);
            services.AddScoped<ILocationRecordRepository, LocationRecordRepository>();
        }
        
        services.AddMvc();
    }
    

    让这些功能最终生效的奇妙之处在于对 AddEntityFrameworkNpgsql 以及 AddDbContext 两个方法的调用

    对真实仓储进行集成测试

    我们想要利用自动的构建流水线,每次运行构建时都启动一个新的、空白的 Postgres 实例

    然后,让集成测试在这个新实例上运行,执行迁移以配置数据库结构

    每次提交代码时,整个过程既要能在本地、团队成员的机器上运行,又要能在云上自动运行

    这就是我喜欢搭配使用 Wercker 和 Docker 的原因

    试运行数据服务

    使用特定参数启动 Postgres

    $ docker run -p 5432:5432 --name some-postgres 
    -e POSTGRES_PASSWORD=inteword -e POSTGRES_USER=integrator 
    -e POSTGRES_DB=locationservice -d postgres
    

    这样就以 some-postgres 为名称启动一个 Postgres 的 Docker 镜像

    为验证能够成功连接到 Postgres,可运行下面的 Docker 命令来启动 psql

    $ docker run -it --rm --link some-postgres:postgres postgres 
    psql -h postgres -U integrator -d locationservice
    

    数据库启动后,还需要表结构,顺便设置了很快会用到的环境变量

    $ exprot TRANSIENT=false
    $ export POSTGRES__CSTR=“Host=localhost;Username=integrator; 
    Password=inteword;Database=locationservice;Port=5432"
    $ dotnet ef database update
    

    我们期望位置服务能够访问到自己的容器之外,并进入 Postgres 容器之内

    容器链接能够实现这项能力,不过需要在启动 Docker 镜像之前就完成环境变量的修改

    $ export POSTGRES__CSTR=“Host=localhost;Username=integrator; 
    Password=inteword;Database=locationservice;Port=5432"
    $ docker run -p 5000:5000 --link some-postgres:psotgres 
    -e TRANSIENT=false -e PORT=5000 
    -e POSTGRES__CSTR dotnetcoreservices/locationservice:latest
    

    使用 psotgres 作为主机名链接 Postgres 容器后,位置服务就应该能够正确连接到数据库了

    为亲自验证结果,可以提交一个位置记录

    $ curl -H "Content-Type:application/json" -X POST -d 
    '{"id":"64c3e69f-1580-4b2f-a9ff-2c5f3b8f0elf","latitude":12.0, 
    "longitude":10.0,"altitude":5.0,"timestamp":0, 
    "memberId":"63e7acf8-8fae-42ec-9349-3c8593ac8292"}' 
    http://localhost:5000/locations/63e7acf8-8fae-42ec-9349-3c8593ac8292
    

    通过服务查询我们虚构的团队成员历史位置

    $ curl http://localhost:5000/locations/63e7acf8-8fae-42ec-9349-3c8593ac8292
    

    为了再次确认,查询 latest 端点并确保仍能获取到期望的输出

    $ curl http://localhost:5000/locations/63e7acf8-8fae-42ec-9349-3c8593ac8292/latest
    

    最后,为了证实确实在使用真实的数据库实例,可以使用 docker ps 以及 docker kill 找到位置服务所在的 Docker 进程并终止它

    然后通过之前用过的命令重新启动服务

    知识共享许可协议

    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

    欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

    如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

  • 相关阅读:
    C++结构体内重载、this指针和友元函数(初步了解)
    数据结构—造树计划—二叉搜索树
    PTA顺序的分数
    PTA兼容任务
    PTA航船
    UML-基于GRASP对象设计步骤
    UML-设计对象时涉及的制品有哪些?
    UML-什么是用例实现(场景实现)?
    UML-如何使用GRASP进行对象设计?
    日志总结
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/12267430.html
Copyright © 2011-2022 走看看