如何避免健谈的界面


10

背景: 我正在设计一个服务器应用程序,并为不同的子系统创建单独的dll。为简化起见,假设我有两个子系统:1)Users2)Projects

用户的公共界面具有如下方法:

IEnumerable<User> GetUser(int id);

而且Projects的公共接口具有如下方法:

IEnumerable<User> GetProjectUsers(int projectId);

因此,例如,当我们需要显示某个项目的用户时,我们可以调用GetProjectUsers,这将为对象提供足够的信息以显示在数据网格或类似物中。

问题: 理想情况下,Projects子系统不应同时存储用户信息,而应仅存储参与项目的用户的ID。为了服务的GetProjectUsers,它需要调用GetUser的的Users系统存储在自己的数据库中的每个用户ID。但是,这需要大量单独的GetUser调用,从而在User子系统内部引起大量单独的sql查询。我还没有真正测试过,但是具有这种健谈的设计会影响系统的可伸缩性。

如果不考虑子系统的分离,我可以将所有信息存储在两个系统Projects都可以访问的单个模式中,并且可以简单地执行a操作,JOIN以在单个查询中获取所有项目用户。Projects还需要知道如何User从查询结果中生成对象。但这打破了具有许多优点的分离。

问题: 有人可以建议一种在避免所有这些单独GetUser通话的同时保持分隔的方法GetProjectUsers吗?


例如,我曾想过让用户为外部系统提供使用标签值对“标记”用户并请求具有特定值的用户的能力,例如:

void AddUserTag(int userId, string tag, string value);
IEnumerable<User> GetUsersByTag(string tag, string value);

然后,Projects系统可以在将每个用户添加到项目中时对其进行标记:

AddUserTag(userId,"project id", myProjectId.ToString());

在GetProjectUsers期间,它可以在一次调用中请求所有项目用户:

var projectUsers = usersService.GetUsersByTag("project id", myProjectId.ToString());

我对此不确定的部分是:是的,用户与项目无关,但实际上有关项目成员资格的信息存储在用户系统中,而不是项目中。我只是感觉不自然,所以我试图确定我是否缺少一个很大的劣势。

Answers:


10

您的系统中缺少的是缓存。

你说:

但是,这需要大量单独的GetUser调用,从而在User子系统内部引起大量单独的sql查询。

方法的调用次数不必与SQL查询的次数相同。您一次获得了有关用户的信息,如果不更改,为什么还要再次查询相同的信息?您甚至很可能甚至将所有用户都缓存在内存中,这将导致SQL查询为零(除非用户进行了更改)。

另一方面,通过使Projects子系统使用来查询项目和用户INNER JOIN,您会引入另一个问题:您正在代码中两个不同位置查询同一条信息,这使得缓存失效非常困难。作为结果:

  • 要么您以后都不会引入缓存,

  • 否则,您将花费数周或数月的时间研究当一条信息发生更改时应该失效的内容,

  • 否则,您将在简单的位置添加缓存无效,而忘记其他位置,从而导致难以发现错误。


重读您的问题,我发现我第一次错过了一个关键词:可扩展性。根据经验,您可以遵循以下模式:

  1. 问问自己系统是否运行缓慢(即,它违反了性能的非功能性要求,或者仅仅是使用时的噩梦)。

    如果系统慢,不操心的性能。关心干净的代码,可读性,可维护性,测试,分支机构覆盖范围,干净的设计,详细且易于理解的文档,良好的代码注释。

  2. 如果是,请搜索瓶颈。您这样做不是通过猜测,而是通过概要分析。通过分析,您可以确定瓶颈的确切位置(假设您猜测时,几乎每次都可能出错),现在可以专注于代码的这一部分。

  3. 一旦发现瓶颈,请寻找解决方案。为此,您可以进行猜测,基准测试,性能分析,编写替代方案,了解编译器优化,了解您所需的优化,在Stack Overflow上提问并使用低级语言(必要时包括Assembler)。

Projects子系统向子系统询问信息的实际问题是什么Users

未来可能出现的可扩展性问题?这不是问题。如果您开始将所有内容合并到一个整体解决方案中,或者从多个位置查询相同的数据,可伸缩性可能会成为一场噩梦(如下所述,由于难以引入缓存)。

如果已经存在明显的性能问题,请在步骤2中搜索瓶颈。

如果看起来确实存在瓶颈,并且是由于Projects通过Users子系统请求用户(并且位于数据库查询级别)而造成的,那么只有在这种情况下,您才应该寻找替代方法。

最常见的替代方法是实施缓存,从而大大减少查询数量。如果您在缓存无济于事的情况下,那么进行进一步的分析可能会表明您需要减少查询数量,添加(或删除)数据库索引,添加更多硬件或重新设计整个系统。 。


除非我对您有误解,否则您是在说“保留单个GetUser调用,但使用缓存避免数据库往返”。
ErenErsönmez15年

@ErenErsönmez:GetUser而不是查询数据库,而是在缓存中查找。这意味着调用多少次实际上并不重要GetUser,因为它将从内存而不是数据库中加载数据(除非缓存已失效)。
Arseni Mourzenko 2015年

鉴于我在突出主要问题上做得不好,这是一个很好的建议,即“无需将系统合并到单个系统中即可摆脱混乱状态”。我的“用户和项目”示例自然会让您相信,相对而言,用户数量很少,而且变化很少。也许更好的例子是文档和项目。想象一下,您有几百万个文档,每天有成千上万个文档被添加,而Project系统使用Document系统存储其文档。那您还建议缓存吗?大概不会吧?
ErenErsönmez2015年

@ErenErsönmez:您拥有的数据越多,显示的缓存就越关键。根据经验,将读取次数与写入次数进行比较。如果每天添加“成千上万”文档,并且每天有数百万个select查询,那么最好使用缓存。另一方面,如果您要向数据库中添加数十亿个实体,但只获得select具有选择性的wheres的数千个实体,则缓存可能没有那么有用。
Arseni Mourzenko 2015年

您可能是对的-我可能正在尝试解决尚未出现的问题。我可能会照原样实施,并在需要时尝试进行改进。如果由于(例如)实体在添加后可能仅被读取1-2次而导致缓存不合适,那么您认为我附加到该问题的可能的解决方案是否可行?您看到一个巨大的问题吗?
ErenErsönmez2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.