What is missing in your system is the cache.
You say:
However, this requires a lot of separate GetUser
calls, causing a lot of separate sql queries inside the User
subsystem.
The number of calls to a method doesn't have to be the same as the number of SQL queries. You get the information about the user once, why would you query for the same information again if it didn't change? Very probably, you may even cache all the users in memory, which would result in zero SQL queries (unless a user changes).
On the other hand, by making Projects
subsystem query both the projects and the users with an INNER JOIN
, you introduce an additional issue: you are querying the same piece of information in two different locations in your code, making cache invalidation extremely difficult. As a consequence:
Either you won't introduce cache at all any time later,
Or you will spend weeks or months studying what should be invalidated when a piece of information changes,
Or you will add cache invalidation in straightforward locations, forgetting the other ones and resulting in difficult to find bugs.
Rereading your question, I notice a keyword I missed the first time: scalability. As a rule of thumb, you may follow the next pattern:
Ask yourself whether the system is slow (i.e. it violates a non-functional requirement of performance, or is simply a nightmare to use).
If the system is not slow, don't bother about performance. Bother about clean code, readability, maintainability, testing, branch coverage, clean design, detailed and easy to understand documentation, good code comments.
If yes, search for the bottleneck. You do that not by guessing, but by profiling. By profiling, you determine the exact location of the bottleneck (given that when you guess, you may nearly every time get it wrong), and may now focus on that part of the code.
Once the bottleneck found, search for solutions. You do that by guessing, benchmarking, profiling, writing alternatives, understanding compiler optimizations, understanding optimizations that are up to you, asking questions on Stack Overflow and moving to low-level languages (including Assembler, when necessary).
What is the actual issue with Projects
subsystem asking for info to Users
subsystem?
The eventual future scalability issue? This is not an issue. Scalability may become a nightmare if you start merging everything into one monolithic solution or querying for the same data from multiple locations (as explained below, because of the difficulty to introduce cache).
If there is already a noticeable performance issue, then, step 2, search for the bottleneck.
If it appears that, indeed, the bottleneck exists and is due to the fact that Projects
requests for users through Users
subsystem (and is situated at database querying level), only then should you search for an alternative.
The most common alternative would be to implement caching, drastically reducing the number of queries. If you're in a situation where caching doesn't help, than further profiling may show you that you need to reduce the number of queries, or add (or remove) database indexes, or throw more hardware, or redesign completely the whole system.
I believe you only need one interface that classes for Polygon and Sprite implement. Based on what I see I would make an interface for GraphicsPacket and create Polygon and Sprite classes that implement it's methods.
Then throughout your code you just write it to use GraphicsPacket objects and you don't need to decide numerous times whether it's a Polygon or Sprite.
public interface IGraphicsPacket
{
public GraphicsPacket(Stream stream);
public GetMesh()
}
public class Polygon : IGraphicsPacket
{
public GraphicsPacket(Stream stream)
{
//Polygon implementation
}
public GetMesh()
{
//Polygon implementation
}
}
public class Sprite : IGraphicsPacket
{
public GraphicsPacket(Stream stream)
{
//Sprite implementation
}
public GetMesh()
{
//Sprite implementation
}
}
TEST
public class Test
{
public void BuildPrimitive([NotNull] IGraphicsPacket primitive)
{
if (primitive == null)
throw new ArgumentNullException(nameof(primitive));
primitive.GetMesh();
return;
}
}
Best Answer
You have two options:
Make a base class, or an abstract base class that implements the common behavior, and inherit from that. This way your code lives in one place and isn't duplicated.
If a base class doesn't work for some reason, you could put the code into a adapter (or strategy) object that each class uses to implement the behavior, again moving the duplicated code to one place.