Permalink
Browse files

nimbus

part 1
  • Loading branch information...
kijanawoodard committed Oct 24, 2013
1 parent 0c4827c commit 73cbeff9998dcead1d7a3da03669486216288d7b
@@ -1,27 +1,30 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Web;
+using System.Collections.Generic;
using System.Web.Mvc;
using Blog.Web.Core;
+using Blog.Web.Infrastructure;
namespace Blog.Web.Actions.AtomGet
{
public class AtomGetController : Controller
{
- private readonly IPostVault _vault;
+ private readonly IMediator _mediator;
- public AtomGetController(IPostVault vault)
- {
- _vault = vault;
- }
+ public AtomGetController(IMediator mediator)
+ {
+ _mediator = mediator;
+ }
public ActionResult Execute()
{
Response.ContentType = " application/atom+xml";
- var model = _vault.ActivePosts;
+ var model = _mediator.Send<AtomRequest, AtomGetViewModel>(new AtomRequest());
return View(model);
}
-
}
+
+ public class AtomRequest { }
+ public class AtomGetViewModel
+ {
+ public IReadOnlyList<Post> Posts { get; set; }
+ }
}
@@ -1,17 +1,17 @@
-@model System.Collections.Generic.IReadOnlyList<Blog.Web.Core.Post>
+@model Blog.Web.Actions.AtomGet.AtomGetViewModel
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Kijana Woodard</title>
<subtitle>Programming and Perceptual Dissonance</subtitle>
<link href="http://kijanawoodard.com/"/>
- <updated>@Model.First().PublishedAtCst.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'")</updated>
+ <updated>@Model.Posts.First().PublishedAtCst.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'")</updated>
<author>
<name>Kijana Woodard</name>
<email>atom@kijanawoodard.com</email>
</author>
<id>urn:http://kijanawoodard.com</id>
- @foreach (var post in Model)
+ @foreach (var post in Model.Posts)
{
<entry>
<title>@post.Title</title>
@@ -1,33 +1,43 @@
-using System.Linq;
+using System;
+using System.Collections.Generic;
using System.Web.Mvc;
using Blog.Web.Core;
+using Blog.Web.Infrastructure;
namespace Blog.Web.Actions.PostGet
{
public class PostGetController : Controller
{
- private readonly IPostVault _vault;
- private readonly IContentStorage _storage;
+ private readonly IMediator _mediator;
- public PostGetController(IPostVault vault, IContentStorage storage)
+ public PostGetController(IMediator mediator)
{
- _vault = vault;
- _storage = storage;
+ _mediator = mediator;
}
- public ActionResult Execute(string slug)
+ public ActionResult Execute(PostRequest request)
{
- var posts = _vault.ActivePosts;
- var post = posts.FirstOrDefault();
- if (slug != null) post = _vault.AllPosts.FirstOrDefault(x => x.Slug.ToLower() == slug.ToLower());
- if (post == null) return HttpNotFound();
+ var model = _mediator.Send<PostRequest, PostGetViewModel>(request);
+ if (model.Post == null) return HttpNotFound();
+ return View(model);
+ }
+ }
- var content = _storage.GetContent(post.FileName);
- var previous = posts.OrderBy(x => x.PublishedAtCst).FirstOrDefault(x => x.PublishedAtCst > post.PublishedAtCst);
- var next = posts.FirstOrDefault(x => x.PublishedAtCst < post.PublishedAtCst);
+ public class PostRequest
+ {
+ public string Slug { get; set; }
+ }
- var model = new PostGetViewModel(post, content, previous, next, _vault.ActivePosts, _vault.FuturePosts);
- return View(model);
- }
- }
+ public class PostGetViewModel
+ {
+ public Post Post { get; set; }
+ public string Content { get; set; }
+ public Post Previous { get; set; }
+ public Post Next { get; set; }
+ public IReadOnlyCollection<Post> Active { get; set; }
+ public IReadOnlyCollection<Post> Future { get; set; }
+
+ public bool HasPrevious { get { return Previous != null; } }
+ public bool HasNext { get { return Next != null; } }
+ }
}
@@ -1,29 +0,0 @@
-using System.Collections.Generic;
-using Blog.Web.Core;
-
-namespace Blog.Web.Actions.PostGet
-{
- public class PostGetViewModel
- {
- public Post Post { get; private set; }
- public string Content { get; private set; }
- public Post Previous { get; private set; }
- public Post Next { get; private set; }
- public IReadOnlyCollection<Post> Active { get; private set; }
- public IReadOnlyCollection<Post> Future { get; set; }
-
- public bool HasPrevious { get { return Previous != null; } }
- public bool HasNext { get { return Next != null; } }
-
- public PostGetViewModel(Post post, string content, Post previous, Post next,
- IReadOnlyCollection<Post> latest, IReadOnlyCollection<Post> future)
- {
- Post = post;
- Content = content;
- Previous = previous;
- Next = next;
- Active = latest;
- Future = future;
- }
- }
-}
@@ -109,12 +109,10 @@
<Compile Include="Actions\AtomGet\AtomGetController.cs" />
<Compile Include="Actions\PostGet\PostGetController.cs" />
<Compile Include="Infrastructure\AlternateViewEngine.cs" />
- <Compile Include="Core\IContentStorage.cs" />
- <Compile Include="Core\IPostVault.cs" />
- <Compile Include="Actions\PostGet\PostGetViewModel.cs" />
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
+ <Compile Include="Infrastructure\nimbus.cs" />
<Compile Include="Initialization\AutofacConfig.cs" />
<Compile Include="Infrastructure\FilteredPostVault.cs" />
<Compile Include="Core\Post.cs" />
@@ -268,6 +266,9 @@
<ItemGroup>
<Content Include="Content\posts\bio.markdown" />
</ItemGroup>
+ <ItemGroup>
+ <Content Include="Content\posts\introducing-nimbus.markdown" />
+ </ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
@@ -0,0 +1,76 @@
+Once I started [questioning IoC] containers, a [variety][violating isp] of [problems][violating srp] presented [themselves][foo ifoo].
+
+Near the end of [questioning IoC], I posited an escape hatch:
+
+ new Mediator(new DoThis(), new DoThat(), new DoTheOther());
+
+Working to achieve this api, I came up with [Nimbus].
+
+To see how it works, I incorporated nimbus to run this blog.
+
+Here is the controller that displays this page [before]:
+
+ public class PostGetController : Controller
+ {
+ private readonly IPostVault _vault;
+ private readonly IContentStorage _storage;
+
+ public PostGetController(IPostVault vault, IContentStorage storage)
+ {
+ _vault = vault;
+ _storage = storage;
+ }
+
+ public ActionResult Execute(string slug)
+ {
+ var posts = _vault.ActivePosts;
+ var post = posts.FirstOrDefault();
+ if (slug != null) post = _vault.AllPosts.FirstOrDefault(x => x.Slug.ToLower() == slug.ToLower());
+ if (post == null) return HttpNotFound();
+
+ var content = _storage.GetContent(post.FileName);
+ var previous = posts.OrderBy(x => x.PublishedAtCst).FirstOrDefault(x => x.PublishedAtCst > post.PublishedAtCst);
+ var next = posts.FirstOrDefault(x => x.PublishedAtCst < post.PublishedAtCst);
+
+ var model = new PostGetViewModel(post, content, previous, next, _vault.ActivePosts, _vault.FuturePosts);
+ return View(model);
+ }
+ }
+
+Here is the controller that displays this page [after]:
+
+ public class PostGetController : Controller
+ {
+ private readonly IMediator _mediator;
+
+ public PostGetController(IMediator mediator)
+ {
+ _mediator = mediator;
+ }
+
+ public ActionResult Execute(PostRequest request)
+ {
+ var model = _mediator.Send<PostRequest, PostGetViewModel>(request);
+ if (model.Post == null) return HttpNotFound();
+ return View(model);
+ }
+ }
+
+The [135 loc source file][nimbus source] is meant for copy/paste inclusion and modification. For instance, say you want to decorate each handler instance with logging/timing/etc. Salt to taste.
+
+It was an interesting journey that led to [other realizations][partial application]. I also noticed how tempting it is to add features.
+
+What kept me in check was [ISP]. I thought [SRP] was a better check, but I kept be able to justify responsibility. Yeah, that feature is close enough. We don't need an entirely different class for just this.
+
+It turned out the ISP kept me on the straight and narrow. After ranting about [violating isp], I could hardly force clients to take dependencies they weren't going to use. That led me to write a bunch of code to cover the signature permutations. The bloat then led me to cut features that weren't truly pertinent.
+
+[questioning ioc]: /questioning-ioc-containers
+[violating isp]: /violating-isp-with-constructor-injection
+[violating srp]: /violating-srp-with-constructor-injection
+[foo ifoo]: /foo-ifoo-is-an-anti-pattern
+[Nimbus]: https://github.com/kijanawoodard/nimbus
+[nimbus source]: https://github.com/kijanawoodard/nimbus/blob/b594b02a5770bf142b19f1ab468967d5f0bab694/src/mediator.cs
+[partial application]: /constructor-injection-is-partial-application
+[isp]: http://en.wikipedia.org/wiki/Interface_segregation_principle
+[srp]: http://en.wikipedia.org/wiki/Single_responsibility_principle
+[before]: https://github.com/kijanawoodard/Blog/blob/300ecdf6b48190849b204dbf0ad20b5c80dfd4f4/src/Blog.Web/Actions/PostGet/PostGetController.cs
@@ -53,6 +53,6 @@ Notice a secondary beneift? You don't need the `IEmailService` interface any lon
Decoupled is better than loosely coupled.
-[violating isp]: /violating-srp
+[violating isp]: /violating-isp-with-constructor-injection
[srp]: http://en.wikipedia.org/wiki/Single_responsibility_principle
[ocp]: http://en.wikipedia.org/wiki/Open/closed_principle
@@ -1,7 +0,0 @@
-namespace Blog.Web.Core
-{
- public interface IContentStorage
- {
- string GetContent(string filename);
- }
-}
@@ -1,11 +0,0 @@
-using System.Collections.Generic;
-
-namespace Blog.Web.Core
-{
- public interface IPostVault
- {
- IReadOnlyList<Post> ActivePosts { get; }
- IReadOnlyList<Post> FuturePosts { get; }
- IReadOnlyList<Post> AllPosts { get; }
- }
-}
@@ -1,15 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Blog.Web.Actions.AtomGet;
+using Blog.Web.Actions.PostGet;
using Blog.Web.Core;
namespace Blog.Web.Infrastructure
{
- public class FilteredPostVault : IPostVault
+ public class FilteredPostVault : IHandleResult<PostRequest, PostGetViewModel>, IHandle<AtomRequest, AtomGetViewModel>
{
- public IReadOnlyList<Post> ActivePosts { get; private set; }
- public IReadOnlyList<Post> FuturePosts { get; private set; }
- public IReadOnlyList<Post> AllPosts { get { return _posts; } }
+ private IReadOnlyList<Post> ActivePosts { get; set; }
+ private IReadOnlyList<Post> FuturePosts { get; set; }
+ private IEnumerable<Post> AllPosts { get { return _posts; } }
public FilteredPostVault()
{
@@ -24,6 +26,29 @@ public FilteredPostVault()
FuturePosts = AllPosts.Except(ActivePosts).ToList();
}
+ public PostGetViewModel Handle(PostRequest message, PostGetViewModel result)
+ {
+ var post = ActivePosts.FirstOrDefault();
+ if (message.Slug != null) post = AllPosts.FirstOrDefault(x => x.Slug.ToLower() == message.Slug.ToLower());
+ if (post == null) return result; //Decision: don't throw, handle downstream as to what this means
+
+ var previous = ActivePosts.OrderBy(x => x.PublishedAtCst).FirstOrDefault(x => x.PublishedAtCst > post.PublishedAtCst);
+ var next = ActivePosts.FirstOrDefault(x => x.PublishedAtCst < post.PublishedAtCst);
+
+ result.Post = post;
+ result.Previous = previous;
+ result.Next = next;
+ result.Active = ActivePosts;
+ result.Future = FuturePosts;
+
+ return result;
+ }
+
+ public AtomGetViewModel Handle(AtomRequest message)
+ {
+ return new AtomGetViewModel {Posts = ActivePosts};
+ }
+
//blind men and the elephant
//make your roles explicit - http://www.infoq.com/presentations/Making-Roles-Explicit-Udi-Dahan#anch41169
@@ -44,6 +69,13 @@ public FilteredPostVault()
private readonly Post[] _posts =
{
+ new Post
+ {
+ Title = "Introducing Nimbus",
+ Slug = "introducing-nimbus",
+ FileName = "introducing-nimbus.markdown",
+ PublishedAtCst = DateTime.Parse("October 24, 2013"),
+ },
new Post
{
Title = "Bio",
@@ -1,10 +1,10 @@
using System.IO;
-using Blog.Web.Core;
+using Blog.Web.Actions.PostGet;
using MarkdownSharp;
namespace Blog.Web.Infrastructure
{
- public class MarkdownContentStorage : IContentStorage
+ public class MarkdownContentStorage : IHandleResult<PostRequest, PostGetViewModel>
{
private readonly string _root;
private readonly Markdown _markdown;
@@ -16,7 +16,14 @@ public MarkdownContentStorage(string root)
}
- public string GetContent(string filename)
+ public PostGetViewModel Handle(PostRequest message, PostGetViewModel result)
+ {
+ if (result.Post == null) return result;
+ result.Content = GetContent(result.Post.FileName);
+ return result;
+ }
+
+ private string GetContent(string filename)
{
filename = Path.Combine(_root, filename);
if (!File.Exists(filename)) return string.Empty;
Oops, something went wrong.

0 comments on commit 73cbeff

Please sign in to comment.