.net 如何在一个干净的架构模式中正确地分离API调用和文件存储服务?

qzwqbdag  于 2023-02-25  发布在  .NET
关注(0)|答案(2)|浏览(98)

我正在进行一个. NET核心项目,需要在其中实现一个清洁架构模式。任务是:
1.向第三方API服务发出HTTP请求
1.将响应内容作为流读取
1.将流内容保存到文件存储
为了解决这个问题,我在基础结构层创建了两个类:

  1. ApiService.cs
public async Task GetDataAsync(string url, Func<Stream, Task> func)
        {
            using (HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(url))
            {
                if (httpResponseMessage.IsSuccessStatusCode)
                {
                    using (Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync())
                    {
                        await func(stream);
                    }
                }
            }
        }
  1. FileStorageService.cs
public async Task CreateFileAsync(string path, Stream content)
        {
            using (FileStream fileStream = new(path, FileMode.Create))
            {
                await content.CopyToAsync(fileStream);
            }
        }

Core Layer中还有第三种方法,将上述两种方法结合起来进行处理:

public async Task DownloadData(string url, string path)
        {
            await apiService.GetDataAsync(url, async stream =>
            {
                await fileStorageService.CreateFileAsync(path, stream);
            });
        }

尽管上面的代码可以工作,但文件存储服务明显直接依赖于API服务。我的期望是以一种方式将文件和API服务完全分离,使两者不需要了解对方。我希望将来能够选择实现管道模式(如果需要),并能够以某种方式定义上面的代码:

string url = "<some_url>";
Stream result = await apiService.GetDataAsync(url);

string path ="<some_path>";
await fileStorageService.CreateFileAsync(path, result);

我试图用API Service Method中的Stream返回类型替换Func〈〉参数,但在正确处理HttpResponseMessage和Stream中的语句时遇到了挑战。
如果你对如何解决上述问题有一些建议,我将非常感激,以便:
1.明确地将API服务与文件服务分开,以便一个服务不知道另一个服务
1.正确处置HttpResponseMessage和来自apiService.GetDataAsync方法的Stream中的Using语句,而不使用Func〈〉(如果可能)
先谢了。

ctehm74n

ctehm74n1#

我不确定你是否考虑过类路径,但我更喜欢的处理方式是有一个StorageFileStorageFolder框架,模仿UWP/Metro/etc从一开始就有的东西。事实上,在dotnet/runtime Github repo中有一个非常古老但仍然悬而未决的问题。
这个想法很简单,就是拥有抽象基类-StorageFileStorageFolder及其父类StorageObject-为您要使用的每个存储平台(从本地文件系统到Azure blob,再到其他任何东西)实现这些抽象基类。
以下是基本定义:

public abstract class StorageObject 
{
    public abstract string Name { get; }
    public abstract Task RenameAsync();
    public abstract Task<StorageProperties> GetPropertiesAsync();   // would retrieve metadata like creation/access dates, size, etc.
    public abstract Task DeleteAsync();
    public abstract Task CopyAsync (StorageFolder destination);
    public abstract Task<IEnumerable<StorageObject>> GetChildrenAsync();
}

public abstract class StorageFolder : StorageObject
{
    public abstract int Files { get; }
    public abstract int Folders { get; }
    public abstract Task<StorageFile> GetFileAsync (string filename, bool createIfNotExists);
    public abstract Task<StorageFolder> GetSubfolderAsync (string subfolderName, bool createIfNotExists);

    public virtual Task<IEnumerable<StorageFile>> FindFilesAsync (string criteria, bool includeSubfolders) { ... } // default implementation would use GetChildrenAsync but be overridable for more efficient implementations
    public virtual Task<IEnumerable<StorageFolder>> FindSubfoldersAsync (string criteria, bool includeSubfolders) { ... } // same
}

public abstract class StorageFile : StorageObject
{
    public abstract Task<Stream> OpenReadAsync(bool shared = false);
    public abstract Task<Stream> OpenWriteAsync(bool shared = false); 
}

例如,LocalStorageFile的基本(不完整)实现如下所示:

public class LocalStorageFile : StorageFile 
{
   private readonly string _path;

   public LocalStorageFile (string path)
   {
      _path = path;
   } 

   public override Task<Stream> OpenReadAsync(bool shared = false)
   {
      var str = File.Open(_path, FileMode.Open, FileAccess.Read, shared ? FileShare.Read : FileShare.None);
      return Task.FromResult(str);
   }

   // ...
}

然后你会这样消费它:

LocalStorageFile f = new LocalStorageFile("c:\\mypath\\file.bin");
using (var str = await f.OpenStreamForReadAsync())
{
   // do your thing
}

最重要的是,代码的其余部分都引用基类-StorageFile等,并且通过处理Stream-另一个高度抽象和可扩展的类-除了Dispose之外,你不需要做任何特殊的事情。当然,如果你有一些高度专门化的东西,你也可以子类化Stream,并从OpenReadAsyncOpenWriteAsync返回你想要的任何东西。

xpszyzbs

xpszyzbs2#

经过评论中的讨论,我认为问题在于apiService正在处理流,然后fileStorageService抛出一个已处理对象异常。
此问题的一个可能的解决方案是删除apiService中的using语句并返回流。

public async Task<Stream> GetDataAsync(string url)
    {
        using (HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(url))
        {
            if (httpResponseMessage.IsSuccessStatusCode)
            {
                return await httpResponseMessage.Content.ReadAsStreamAsync();
            }
        }
    }

然后,您可以按以下方式使用它:

string url = "<some_url>";
Stream result = await apiService.GetDataAsync(url);

using(result)
{
    string path ="<some_path>";
    await fileStorageService.CreateFileAsync(path, result);
}

相关问题