繁体   English   中英

业务对象和多个数据源

[英]Business objects and multiple data sources

在WPF应用程序上,我创建了两个单独的项目:UI(带有XAML和ViewModels)和“ Core”(人们称之为“域对象”或“业务对象”的对象),这些对象代表了我所使用的离散概念案例)。 我知道这是一个好习惯。

但是我的许多业务对象都与多个数据源进行交互。 例如,一个Document对象可能包含来自数据库以及来自文件的数据。

然后,您可以使用Document “执行”的某些操作(我作为Document方法实现)涉及其他资源。 例如, Document.SubmitForProcessing()调用Web服务。

所以我这样写:

public class Document
{
    public string Name { get; set; }
    public string FilePath { get; set; }
    public string FileData { get; set; }

    private Document() { }

    public static Document GetByID(int documentID, string databaseConnectionString, string baseFilePath)
    {
        // Using Dapper
        Document newDoc = db.Query<Document>("SELECT Name, FilePath FROM Documents WHERE ID = @pID", new { pID = documentID });

        newDoc.FileData = File.ReadAllText(Path.Combine(basePath, newDoc.FilePath));

        return newDoc;
    }

    public void SubmitForProcessing(IWebService webService)
    {
        webService.ExecuteFoo(this.Name, this.FileData);
    }

    public void DoBusinessStuff()
    {
        this.FileData = this.FileData.Replace("foo", "bar");
    }
}

毋庸置疑,这真的很快就令人讨厌,并且很难针对它编写测试。

因此,我阅读了有关依赖项注入和存储库模式的信息。 但是我不确定在这种情况下如何正确执行。 我是否为每个数据源都有单独的存储库类,然后是否有某种DocumentFactory或某种访问单独存储库并将Document对象缝合在一起的东西? 还是有更简单的方法?

我主要关心的是使代码易于测试,这样我就可以编写一些单元测试,而无需模拟整个数据库和文件系统,而且还可以停止向我拥有的每个工厂方法(例如, GetByID(int documentID, string databaseConnectionString, string baseFilePath) -我的现实生活中有超过6个这样的参数)。

在类似的问题上,答案涉及诸如SOLID,YAGNI,用于CRUD的存储库之类的东西。我重视这些原则,但是我很难从中获得实用的设计。 例如,Web服务并不是真正的CRUD-y。 我是否有一个“存储库”,以便可以在单元测试期间将其切换出来? 那文件系统呢?

TL; DR-该代码有什么问题?

指导表示赞赏。 谢谢!

如果您不使用某些高级单元测试工具(例如,存根: https : //msdn.microsoft.com/zh-cn/library/ff798446.aspx ),则静态方法和I / O函数自然很难进行测试。 您面临的问题是您有一个调用两个I / O函数的静态方法。 目的是使它们解耦。

我要做的第一件事是将GetById方法重构为Factory类。 您可以通过将数据库和文件系统I / O实现作为接口传递来创建工厂类。 使用接口的优点是它允许您模拟I / O行为。 对于像下面这样的简单接口,我什至可以简单地在测试代码中实现它们,而无需任何模拟。 这样,您可以将GetById方法的业务逻辑隔离到Factory类,并且不必费心测试I / O本身,因为它是由数据库提供程序和win32 api完成的。 这就是您的全部需求。

class Document
{
    public string FileData { get; set; }
    public string FileRelativePath { get; set; }
}

interface IDocumentRepository
{
    Document Get(int id);
}


abstract class  DocumentFactory
{
    public abstract Document Create(int docId);
}

interface IFileStore
{
    string Read(string fileName);
}

class ConcreteDocumentFactory : DocumentFactory
{
    private IDocumentRepository _db;
    private IFileStore _fileStore;

    public ConcreteDocumentFactory(IDocumentRepository db, IFileStore fileStore)
    {
        _db = db;
        _fileStore = fileStore;
    }

    public override Document Create(int docId)
    {

        Document newDoc = _db.Get(docId);
        newDoc.FileData = _fileStore.Read(newDoc.FileRelativePath);
        return newDoc;
    }
}


/////// Test Code Below

[TestFixture]
class TestClass
{

    class TestFriendlyFileStore : IFileStore
    {
        public string Read(string fileName)
        {

            if (fileName == "sample.txt")
                return "Some File Content";
            throw new Exception("Not good file name.");
        }
    }


    class TestFriendlyDocRepo : IDocumentRepository
    {
        public Document Get(int id)
        {
            if (id != 999)
                return new Document() {FileRelativePath = "sample.txt"};
            throw new Exception("Not good id.");

        }
    }

    [Test]
    public void Test()
    {
        var concreteDocFactory = new ConcreteDocumentFactory(new TestFriendlyDocRepo(), new TestFriendlyFileStore());
        var doc = concreteDocFactory.Create(999);
        Assert.AreEqual(doc.FileData == "Some File Content")

    }
}

如上一个答案所述,它仅适用于测试您的业务类,并且仅捕获来自基础结构,数据库或Web服务的预期异常(仅在客户端代码上调用),因此,我建议您设计与基础结构无关的软件以使其具备坚固且可测试:

public class Document : IAcceptDocumentVisitor
{
    public int Id { get; private set; }
    public string Name { get; private set; }
    public string FilePath { get; private set; }
    public string FileData { get; private set; }

    public Document(int id, string name, string filePath, string fileData)
    {
        Id = id;
        Name = name;
        FilePath = filePath;
        FileData = fileData;
    }

    /// <summary>
    /// This method replace SubmitForProcessing
    /// </summary>
    /// <param name="visitor"></param>
    public void Accept(IDocumentVisitor visitor)
    {
        if (visitor == null) throw new ArgumentNullException(nameof(visitor));
        visitor.Visit(Name, FileData);
    }

    public void ReplaceFileData(string fileData, Action onSuccess)
    {
        //Business valdation 
        var validate = true;
        //Business valdation 
        if (!validate) return;
        FileData = fileData;
        onSuccess();
    }
}

public interface IAcceptDocumentVisitor
{
    void Accept(IDocumentVisitor visitor);
}
public interface IDocumentVisitor
{
    void Visit(string name, string fileData);
}

public class FakeWebServiceVisitor : IDocumentVisitor
{
    public void Visit(string name, string fileData)
    {
        Name = name;
        FileData = fileData;
    }

    public string FileData { get; set; }

    public string Name { get; set; }
}

public class WebServiceVisitor : IDocumentVisitor
{
    public void Visit(string name, string fileData)
    {
        //Call web service
        //webService.ExecuteFoo(this.Name, this.FileData);
    }
}

public interface IDocumentReader
{
    Document GetById(int id);
}

public class DocumentDbReader : IDocumentReader
{
    public Document GetById(int id)
    {
        //Get from database
        //Document newDoc = db.Query<Document>("SELECT Name, FilePath FROM Documents WHERE ID = @pID", new { pID = documentID });
        return new Document(id, "Name", "Path", "Data");
    }
}

Usign的一些OOP技术和方法,例如存储库,访问者,CQRS和solid,您可以享受诸如SOLID代码之类的好处,并且还将开始使您的代码可测试:

[TestClass]
public class DocumentSpecs
{
    public string Name = "Name";
    public string FilePath = "Path";
    public string FileData = "Data";
    [TestMethod]
    public void AcceptVisitorCorrectly()
    {
        //Arrange
        var document = new DocumentDbReader().GetById(0);
        var visitor = new FakeWebServiceVisitor();
        //Act
        document.Accept(visitor);
        //Assert
        Assert.AreEqual(FileData, visitor.FileData);
        Assert.AreEqual(Name, visitor.Name);
    }

    [TestMethod]
    public void ReplaceFileDataCorrectly()
    {
        //Arrange
        var successActionCalled = false;
        var expectedFileData = Guid.NewGuid().ToString();
        var document = new DocumentDbReader().GetById(0);
        //Act
        var documentInitialData = document.FileData;
        document.ReplaceFileData(expectedFileData, () => successActionCalled = true);
        //Assert
        Assert.IsTrue(successActionCalled);
        Assert.AreEqual(expectedFileData, document.FileData);
        Assert.IsFalse(documentInitialData == document.FileData);
    }    
}

结果如下:

检测结果

亲切的问候!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM