Friday, May 11, 2012

Reviewing Xenta and wishing I hadn’t

Reviewing Xenta and wishing I hadn’t:
Xenta Framework is the extensible enterprise n-tier application framework with multilayered architecture.
I was asked by the coordinator for the project to review it.
This isn’t going to take long. I looked at the code, and I got this:
image
I am sure you remember the last time when I run into something like this, except that the number of projects at that solution was a quarter of what we had here, and I already had to hold my nose.
Looking a bit deeper:
image
Here is a hint, you are allowed to have more than one class per project.
Having that many project is a nightmare in trying to manage them. Finding things, actually making use of how things work. Not to mention that the level of abstractness required to support that is giving me a headache.
This is still without looking at the code, mind.
Now, let us look at the actual code. I like to start the controllers. The following code is from ForumController:

[HttpPost, ValidateInput(false)]
public ActionResult Update(int forumID, FormCollection form)
{
ForumModel m = new ForumModel()
{
ForumID = forumID
};

if(!m.Load())
{
return HttpNotFound();
}

if(TryUpdateModel<ForumModel>(m, "Model", form))
{
try
{
m.Update();
}
catch(Exception ex)
{
ModelState.AddModelError("API", ex);
}
}

if(!ModelState.IsValid)
{
TempData.OperationStatus("Failed");
TempData.PersistObject("Model", m);
TempData.PersistModelState(ModelState);
}
else
{
TempData.OperationStatus("Success");
}

return RedirectToAction("Edit", new
{
ForumID = m.ForumID
});
}



Seriously, I haven’t seen this style of architecture in a while. Let us dig deeper:

public bool Update()
{
using(ForumApiClient api = new ForumApiClient())
{
var dto = api.UpdateForum(ForumID, ParentForumID, DisplayOrder, Flags);

return Load(dto);
}
}

public bool Load()
{
using(ForumApiClient api = new ForumApiClient())
{
var dto = api.GetForum(ForumID);

return Load(dto);
}
}



In case you are wondering, those are on the ForumModel class.
This piece of code is from the ForumPostModel class, it may look familiar:

public bool Load()
{
using(ForumApiClient api = new ForumApiClient())
{
var dto = api.GetPost(PostID);

return Load(dto);
}
}



This ForumApiClient is actually a WCF Proxy class, which leads us to this interface:
image
I won’t comment on this any further, but will go directly to ForumApiService, where we actually update a forum using the following code:

public ForumDto UpdateForum(int forumID,
int parentForumID,
int displayOrder,
ForumFlags flags)
{
ForumService forumService = Infrastructure.Component<ForumService>();

if(forumService == null)
{
throw new FaultException(new FaultReason("forum service"), new FaultCode(ErrorCode.Infrastructure.ToString()));
}

try
{
ForumEntity entity = forumService.UpdateForum(forumID, parentForumID, displayOrder, flags, DateTime.UtcNow);

return Map(entity);
}
catch(XentaException ex)
{
throw new FaultException(new FaultReason(ex.Message), new FaultCode(ex.Code.ToString()));
}
}



Infrastructure.Component represent a home grown service locator. Note that we have manual exception handling for absolutely no reason whatsoever (you can do this in a behavior once, and since this is repeated for each and every one of the methods…).
I apologize in advance, but here is the full UpdateForum method:

public ForumEntity UpdateForum(int forumID,
int parentForumID,
int displayOrder,
ForumFlags flags,
DateTime updatedOn)
{
ForumEntity oldForum = GetForum(forumID);

if(oldForum == null)
{
throw new XentaException(ErrorCode.NotFound, "forum");
}

ForumEntity parentForum = null;

if(parentForumID != 0)
{
parentForum = GetForum(parentForumID);

if(parentForum == null)
{
throw new XentaException(ErrorCode.NotFound, "forum");
}

ForumEntity tmp = parentForum;

HierarchyCheck:

if(tmp.ForumID == forumID)
{
throw new XentaException(ErrorCode.InvalidArgument, "parentForumID");
}
if(tmp.ParentForumID != 0)
{
tmp = tmp.Parent;

goto HierarchyCheck;
}
}

ForumEntity newForum = null;
bool res = Provider.UpdateForum(forumID,
parentForumID,
oldForum.LastTopicID,
oldForum.TopicCount,
oldForum.PostCount,
displayOrder,
flags,
oldForum.CreatedOn,
updatedOn);

if(res)
{
if(Cache != null)
{
Cache.Remove("ForumService_GetForum_{0}", oldForum.ForumID);
}

newForum = GetForum(forumID);

if(EventBroker != null)
{
EventBroker.Publish<PostEntityUpdate<ForumEntity>>(this, new PostEntityUpdate<ForumEntity>(newForum, oldForum));
}
}
return newForum;
}



Yes, it is a goto there, for the life of me I can’t figure out why.
Note that there is also this Provider in here, which is an IForumProvider, which is implemented by… ForumProvider, which looks like this:

public bool UpdateForum(int forumID,
int parentForumID,
int lastTopicID,
int topicCount,
int postCount,
int displayOrder,
ForumFlags flags,
DateTime createdOn,
DateTime updatedOn)
{
bool res = false;

using(DbCommand cmd = DataSource.GetStoredProcCommand("fwk_Forums_Update"))
{
SqlServerHelper.SetInt32(DataSource, cmd, "ForumID", forumID);
SqlServerHelper.SetInt32(DataSource, cmd, "ParentForumID", parentForumID);
SqlServerHelper.SetInt32(DataSource, cmd, "LastTopicID", lastTopicID);
SqlServerHelper.SetInt32(DataSource, cmd, "TopicCount", topicCount);
SqlServerHelper.SetInt32(DataSource, cmd, "PostCount", postCount);
SqlServerHelper.SetInt32(DataSource, cmd, "DisplayOrder", displayOrder);
SqlServerHelper.SetInt32(DataSource, cmd, "Flags", (int)flags);
SqlServerHelper.SetDateTime(DataSource, cmd, "CreatedOn", createdOn);
SqlServerHelper.SetDateTime(DataSource, cmd, "UpdatedOn", updatedOn);

res = DataSource.ExecuteNonQuery(cmd) > 0;
}
return res;
}



And… this is about it guys.
I checked the source control, and the source control history for this goes back only to the beginning of February 2012. I assume that this is older than this, because the codebase is pretty large.
But to summarize, what we actually have is a highly abstracted project, a lot of abstraction. A lot of really bad code even if you ignore the abstractions, seemingly modern codebase that still uses direct ADO.Net for pretty much everything, putting a WCF service in the middle of the application just for the fun of it.
Tons of code dedicated to error handling, caching, etc. All of which can be handled as cross cutting concerns.
This is the kind of application that I would expect to see circa 2002, not a decade later.
And please note, I actually got the review request exactly 34 minutes ago. I didn’t review the entire application (nor do I intend to). I merely took a reprehensive* vertical slide of the app and followed up on that.
*Yes, this is intentional.


ICT4PE&D

No comments:

Post a Comment

Thank's!