Here's a quick example of how to write a caching aspect with Castle DynamicProxy.
First, let's write an implementation and a service worth caching:
Next, you need to decide where to cache the results. If you're using a web app, maybe try ASP.NET's Cache object. If you're writing an Azure app, try AppFabric caching. You can cache things in a database, a text file, whatever is appropriate for your application.
For this simple example, I'm going to cache everything in memory, in a static Dictionary object. Also, nothing that goes into my cache will ever expire or be invalidated. Once it's cached, it's cached forever. Also, it's not thread safe. Not a very useful cache in a real app, so let me just be clear: do not use this in a production application. It's only for demonstration: whatever you use for caching is up to you, I'm just demonstrating the aspect part.
I'm generating the cache key by appending the argument values to the method name. So if you call MyMethod("test") and MyMethod("test2"), that's two different keys that will cache two different results. This might work for you, or you might need a more complex GenerateCacheKey method (something that uniquely identifies objects, perhaps).
Next, I wire up my IoC container to return MyService when asked for an implementation of IMyService. But I also have to make sure that my IoC container (StructureMap in this case) applies the caching interceptor to it.
I put it all together in a simple console app:
And here's the result:
Do you agree with these concepts?
TDD is great, BDD is better. DDD is the way it should be done. SOLID principles should always be followed. Don't repeat yourself. Be agile. Follow the boy scout rule. Use an IoC tool and/or dependency injection. Don't reinvent the wheel. Always normalize your SQL tables. Use AOP to avoid boilerplate and clutter.
I do. And a lot of other people do too. But why? Because:
- ...it's been drilled into your head by peers and at software conferences?
- ...you have baggage from a previous job or hate your current job?
- ...you read about them all in a book like Clean Code, and assumed Uncle Bob knows what he's talking about?
Or have you actually researched and practiced each of these concepts and found them to be superior in many cases to the alternatives (which you've also researched and practiced)?
Well, for me, the answer is: all of the above. Except I don't hate my (current) job.
Yes, I just admitted that I've blindly subscribed to a lot of the tenets that you probably hold dear because of peer pressure and authority figures. This isn't necessarily a bad idea: authority figures and peer pressure can be useful constructs. But independent of your own healthy skepticism and critical thought, they can be dangerous too.
So before you run with any new acronym like BDD or AOP, do your research: see what your peers think, see what authority figures think, and finally use your own brain to put it all together.
One of my favorite acronyms is "RTRJ" for "Right Tool for the Right Job". At worst, it's something of a cliche, and sometimes even an excuse or blanket answer. But I think it's an important thing to keep in mind whenever you are thinking about architecture of an application. If there was a perfect framework or tool for every job, then architecture would be easy because then every developer would always be on the same page.
I came across this article on CodeProject about making architectural decisions and choosing technologies for a .NET project. While you may not agree with every choice made for that project just from reading, I think it's interesting and important to note why each decision was made. For instance, the author was looking at IoC/AOP tools for the project, and considered many popular tools like Spring.NET, LinFu, and PostSharp. Ultimately, the author determined that Spring.NET was the best choice because of its documentation, integration with NHibernate, and the fact that it also comes with dependency injection capabilities. The author also notes the downsides and risks to using Spring.NET.
I think "Right Tool for the Right Job" consists of two main concepts: understanding the job and choosing the right tools. Everyone can probably rattle off their favorite tools, but are all of those tools correct for this project?
- Is the rest of the team more familiar with and comfortable with a different tool?
- Will this project be cheaper or faster to finish with a certain tool?
- What about support: is an older tool with a bigger support community better than a newer tool that doesn't have a big following yet?
- Are the benefits of new tool going to outweigh the uphill challenge of learning curves and convincing others to adopt it?
- Biases: even if you don't admit you have them, you have them. Figure out a good way to make your analysis objective: have others on the team do the spike and get their opinions. Try to play devil's advocate. Find someone to ask you the hard questions and find the answers.
- Is tool X being used just because it's always been used before? There's probably a good reason for that, but don't be afraid to challenge assumptions and the status quo, even if it does end up being quo.
Above all, don't forget: shipping is a feature. You could spend eternity trying to figure out just the perfect combination of tools and architecture, but ultimately the best way to get feedback is to get building! If you find yourself putting a square peg in a round hole during your first sprint (I'm assuming you are using some sort of agile methodology), say so at your retrospective and switch to something else while you still can.
When deploying this site for the first time and actually telling people to come here, I learned that I had made a pretty big mistake. Appropriately (or perhaps tragically), I made a mistake in one of the cross-cutting concerns of my own site.
I am using StructureMap to wire up all my dependencies, and 90% of these dependencies are just done WithDefaultConventions. But there is one dependency, IDbConnection which I wired up by hand, like so:
If you can spot the mistake here, then congratulations, you are officially a better developer than me (which is no high accolade). Here's what I should have done:
And then, in Application_EndRequest, I just call ReleaseAndDisposeAllHttpScopedObjects() on ObjectFactory. This means each of the SqlConnections will be bound to a request, and then disposed of when the request is complete. Because I wasn't doing that before, each of the connections would stay open. I didn't notice this when developing the site, because I was continually rebuilding and redeploying.
This may not be strictly speaking AOP, but it's certainly a cross-cutting concern, as almost every request makes use of the IDbConnection to use Dapper, and using StructureMap to manage that dependency saves me from writing a bunch of boilerplate "new SqlConnection" in all my repositories.
Lesson learned: if this site was actually mission critical to a business, I would've been in hot water. I should've been testing with a larger number of requests over a longer running application in an environment similar to production. Fortunately, since I didn't have a bunch of repetitive boilerplate, it was very easy for me to make the fix, once I figured out what the problem was.