I
work as a Software Architect/Systems Architect and many times when I do job
interviews it seems that people simply have no clue about what I do. I can't
blame them as such terms have many meanings and if you look in wikipedia links Software
Architect and Systems
Architect you will find that some of them seem to be completely
different tasks.
In
fact, the entire problem lies on the fact that almost any decision made before
actually writing some code may be seen as architecture. If I decide to create a
game, deciding which kind of game will be created is already a decision of
architecture. It is not software architecture by itself, yet such an initial
decision will affect the programming that will be done later, as different
kinds of game require different kinds of decisions.
But
we usually start to talk about software architecture when we start to choose
the technologies to be used. That is, will we use XNA? Will we use C#? Will we use Javascript?
If
we decide it will be C# but not XNA, will it be Windows Forms? WPF? Silverlight?
And,
if it is a multi-player game, will we use pure TCP/IP (or UDP) writing all the communication layers/details or
will we use a high-level framework like WCF?
This
is a very important decision time, as the entire evolution of the application
may go better or worse by those initial decisions. Yet, except in the situation
that we decide to write the entire communication on our own we are in a moment
to "choose" from existing technologies, not to think about how to
create them.
And,
the worst truth is: Usually, independently on which chooses we make at this
point, the application can still be developed. As I just said, the entire
evolution of the application may go better or worse, but it will "be
possible".
And
apparently that's what most architects do: They choose technologies to write
new applications. And something that makes me sad is that they usually don't
think about the problem at all, they simply use extremely basic conditions as
the parameter to their decisions, like:
·
If it is a game, use XNA as it is optimized for games;
·
If it is a local
application, use WPF (if they love new
technologies) / use Windows Forms (if
they prefer old technologies);
·
If the applications
need to communicate with each other, use WCF.
And
after those decisions (that is, after the initial "architecture"),
they keep working, having to find "work-arounds" over usually bad
decisions (or the lack of decisions) they did at the initial stage. After all,
if the initial decisions were all right and they aren't going to develop their
own framework, why will they continue to work in the project?
Frameworks
I
was just saying that the initial stage is to usually choose technologies, like WCF, WPF and the
like. For web-sites that will be something like ASP.NET +
MVC, Web Forms, caching technologies and
the like. All of those technologies can be seen as "frameworks" to do
one kind of job.
Well,
as an architect I usually have the job of creating frameworks like these. My
purpose is rarely to choose the best existing framework, but to make the right
decisions to create frameworks like these that work correctly (with good
performance, memory consumption, ease of use and most important of all: really
expandable).
But
I think that you may be scared already: If I want to create a game, will I lose
time creating all the technology? That's crazy!
I
can agree that for a small project it may seem crazy to write an entire
technology when there are others already available. But, first, that's my
specialty. Maybe it is not what a company is looking for. Second, in many large
projects creating the technology, even if it starts redirecting to another one,
opens new possibilities. In fact, I started to create frameworks because most
of the time I simply considered the architecture of the already existing ones
terrible. It doesn't mean they don't work. It simply means they weren't really
helping or making things easier and, in many situations, they were limiting
what can be achieved.
But
before explaining the problems or the solutions, I will try to explain my view
on what is a "framework".
What is a framework?
I
frequently see a definition that "you call a library, a framework calls
you" and, even if it is OK in the sense that when you use a framework you
must "obey" its rules, usually filling events or implementing virtual
methods that will be called by the framework, it is very problematic in the
sense that some classes may be used directly (like a "library") or
inherited (so the virtual methods will be called like a "framework").
Also,
any DLL is a library (that's the meaning of the last L), which can contain one
or more "frameworks".
So,
I prefer to say that there are frameworks in the general sense and in the
specific sense. That is, a DLL created to contain a framework "is a
framework", but in fact such library can contain isolated classes, usable
by any applications, the main framework and even "secondary
frameworks".
That
is, any solution to a kind of problem, be it build of a single very useful
class or by a collection of many classes may be considered a framework. A
framework usually has many classes, but in your initial use you may only use
the basic methods provided by a single class and only later you may use the
extra functionalities.
For
example: When you use the BinarySerializer class you are using the basic
serialization provided by .NET. But you can create your own serializable
classes by using the [Serializable] attribute and even by implementing the ISerializable interface.
So, there is an entire framework, but in your initial case you may be using it
as a simple "library" class.
A common error: Creating your own framework is
bad
The
normal arguments I see against frameworks are:
·
A framework forces
your application to work in a specific direction, avoiding you from doing
anything different;
·
The code of a
framework to solve a problem is harder than solving the problem directly so, in
most cases, creating a framework will only add complexity to the project;
·
Using the previous
definitions, some people say: "Create a library, not a framework";
·
Your framework will
never be as feature complete as a framework made by a company dedicated to do
that;
·
If you quit the
company, who will maintain the framework? By buying it from a company we have
the guarantee that we will have support.
And
I must say that I mostly agree with all the arguments. But the truth is: Any
big project ends-up having a framework, be it a well architected one, be it a
messed-up one made on top of other frameworks (and that's what some developers
that hate frameworks usually do).
That
is, developers that avoid creating a framework to buy an external one usually
finish with their own framework, based on an external one and it usually has
the original limitations + the limitations they may have added to it.
The
entire idea is that by using software made by a company we have a better
support, better quality etc. But a company dedicated to create a technology
don't know our specific needs, so they will give us some "generic"
solution. Unskilled programmers may try to do the same and they may end-up
doing a very poor job. Very experienced developers may make a better solution
for the company, even if it is not as feature complete as the one bought from
another company.
So,
if you are a really experienced developer (or if you have really experienced
developers working for you), it may worth to let them create a framework
specific to the company's need.
Architecture - The Bad Ones
When
I think about a project, I usually start by thinking what I want to do, then I
think the things needed to do the job (the concept of a technology, not the
technology itself) and only later I think about the existing technologies that
may help me in doing that.
But
because I already thought about possible needed technologies without thinking
about a specific one, I didn't think about any limitations, any technology
specific data or any work-arounds. Then, when I see the existing technologies,
what usually makes me decide to create a framework on my own is that those
technologies expect the application to be done "to use them" and,
even if using one technology in this situation is acceptable, I can't put two
external technologies (frameworks) to work together, as one doesn't know about
the existence of the other and that's a requirement of the other framework.
What I mean by "they expect the application to use
them"?
Well,
they expect that your code is written to:
·
Inherit from their
base classes;
·
Implement their
interfaces;
·
Use their attributes;
·
Or anything like this,
which requires the code to be compiled with a reference to them.
And
so, if you use objects of Framework A (which doesn't know about Framework B)
you can't use those objects with the Framework B if you don't create adapters.
Creating
adapters work but, in some cases, it is a waste of time. When we use a
framework like Serialization we want to "convert object instances to
bytes" without caring how to do it. But if we need to create an adapter
that's serializable, why not write the serialization by hand? And worst, you
may have a really big graph of objects, and only one of the objects may not be
marked as [Serializable], even if it is extremely easy to make it serializable. But you
still need to recreate the entire "adapted" graph to solve such a
problem as you can't change the source code of an external framework.
And,
if you think you can create something to automate the entire adapted graph, you
will be creating a "framework" to create adapters. So, why not create
the right framework directly?
Note: I already talked
about how the attributes violate the Single Responsibility Principle in
the article Attributes vs. Single Responsibility Principle. There
some people argued that [Attribute]s aren't code. That they are attached to
classes/properties and not part of them. Yet, consider the problem of
third-party libraries. You can't change their source code to add the attributes
and you can't add attributes at run-time (well, at least not until .NET 4... I
am not sure if that's possible in .NET 4.5).
Also,
there other kinds of problem. Usually they aren't as bad, but I consider them
to be very annoying. This happens on frameworks that expect to find some
configuration directly in the configuration file, without giving you a chance
to set such a configuration from code or on frameworks that do some kind of
action automatically but don't allow you to extend such an action, only to
replace it (and worst, that usually must be made instance by instance when a
global extensibility point would be better).
So,
which frameworks I consider problematic? Most of them, even if they are world
widely used.
This
includes:
·
WPF
"convert" bindings;
·
The TypeConverters
in general;
·
Default binary
serialization of .NET;
·
Default XML
serialization of .NET;
·
WCF attribute-driven
architecture;
·
MarshalByRefObject and all the classes that already inherit
from or depend on it;
·
Most ORM frameworks
which usually are attribute based, configuration file(s) based and constrained
to database-types requiring adapters to be created if we want the data to be
presented with application-specific data-types.
I
am not saying that those frameworks don't work. They work. But they could be
better.
So,
explaining each point:
·
If you don't specify a Converter in a WPF
binding, it is able to do some automatic conversion. I think it is able to use
the [TypeConverter]s,
which are already limited. But you can't register a converter from type A to B
to be global to your application if those types are from unrelated assemblies;
·
The [TypeConverter]s
in theory can convert any type to any type, but they require the attribute to
be used in one of the two types (be it the source or the destination) and a
single type-converter must know all the possible conversions. So, considering that
we may have types that are easy to convert from one to the other but are coming
from unrelated libraries, we are stuck. So those type-converters end-up used
only to convert to and from strings or some of the primitive types;
·
The .NET Binary
serialization can't serialize a type that's not marked as [Serializable].
It is not important if you know how to serialize it. This is even worse if only
the deeper level of a big graph is non-serializable;
·
The .NET xml
serialization doesn't share the binary serialization attributes, so if you
create a class that can be serialized by both you need to remember to use the
attributes for both;
·
What can I say, you
can't get a component that's already made to work as a service (for example,
stateless and using only basic data-types) if it doesn't use all the
"contracts" expected by WCF. I will explain a little more on this
later;
·
The entire idea of the MarshalByRefObject is
that all the calls become virtual, even if you mark the class as sealed, so they could be
"replaced". Well, interfaces are purely virtual, but with an
interface you have the option to use the calls as purely virtual or to continue
using the rightly typed class, avoiding any virtual call. With the MarshalByRefObject you
always end-up doing virtual calls, even when you don't want to. So, tell me how
many times did you open a file (FileStream) and really expected it to be replaced by
another class? Why not use a Stream (or better, if it existed, an IStream) when you want any
stream, and use non-virtual calls to the FileStream when you know its exact type?
Unfortunately, by being a MarshalByRefObject you can declare the variable with the
real sealed type and the virtual calls will continue to be done.
IoC - Inversion of Control
Maybe
I am getting a little off-topic here, but another thing that annoys me is the
now popular idea of Inversion of Control.
In fact, I already consider it a bad name. If the correct architecture is to
"invert control" and people respects it, then it becomes the
"normal", not the "inverted" architecture.
To
allow such an inversion of control it is recommended that you only depend on
"interfaces" not on "implementations", but such a solution
is not the best solution all the time. Some components may expect to work only
with their "family" components, not with any other component. So, if
you use IoC with them, you must use IoC for the entire family, effectively being able
to replace one family by another one, not to replace individual components.
That's
the case with ADO.NET connections, commands, parameters and the like. You can
replace the entire SQL Server family
by the entire Oracle family, but you can't
replace only the connection without replacing the other components. So, if you
are not writing the application that uses components like that, but writing
such components (I mean, any component family or framework) you don't really
require to make one component to talk to the others only by the interface.
Having the interface is good to avoid the need for adapters if you want to
replace the entire "family", but the components can talk to each
other knowing by their right types.
In
fact, the best architecture in such a case is to have the interfaces declared
in a common assembly (DLL) and to implement the specific "families"
in other DLLs. The users will then be able to depend only on the common
assembly and thanks to an IoC container
choose which "family" to use at run-time. But each family can be
written depending directly on their family components, avoiding the interfaces,
the virtual calls, having access to internal fields, properties and methods and also
avoiding the IoC completely.
So,
if you think that you should make every class only talk to other classes by
interfaces, well, think again.
A Note About "Component Families"
I
just talked about ADO.NET to explain the component families and a thing I see
very frequently is a solution that "loads" drivers using a
"rigid" rule.
That
is, ADO.NET uses entries in the configuration file (and the Machine.config) to
search for database drivers by name if you use the DbProviderFactories.GetFactory() method. That's a extremely rigid rule. The application
can't tell how to search the drivers differently.
Even
if you can load the drivers without using the DbProviderFactories, remember about such a problem if you create your own
"basic solution" capable of loading drivers. You may look for drivers
locally or by using some rigid rule like that but, if one isn't found, allow an
event to do the search. The AppDomain.AssemblyResolve is an example of how you can create an
event to solve that "missing information" problem and it already
allows some clever usages, like embedding the libraries into the application
while allowing them to be found only when requested.
Architecture - First "Fix"
I
know that most of us simply can't solve the architecture problems of already
existing frameworks. But, if you work in the creation of some framework,
there's an "easy-fix" to most of the problems, and it is very similar
to the AssemblyResolve event: Call an event to try to do the job before failing.
If
we see what's happening in most cases, it is like this: A framework wants some
more information to finish its job and to find such an information it may:
·
Read a configuration
file;
·
Read an attribute;
·
Cast your instance to
an interface.
And,
if it isn't able to do that, it simply fails/throws an exception.
So,
why not call an event at that moment, giving all the information you already
have (that is, the instance you are working on, the action you want and the
parameters you already have, like a conversion from a value X to a specific type) and let the event tell you
if it was able to do the job or not?
Only
in a situation where the event doesn't do the job you generate the
error/exception.
This
will solve the .NET binary serialization, the XML serialization, may enable WCF
to use types that don't have the right attributes and all of that. And, the
best of all: As it is not a change to existing methods, but a new event, it
will not cause a breaking change as old code will simply ignore the existence
of such an event.
Only
to finish explaining the fix comparing to the previously presented problems,
the MarshalByRefObject is
from another kind, which can be solved by using interfaces. And about the ORMs,
well, there are many ORMs with different kinds of problems, some of them will
benefit from such an event call.
Improvement to the first fix.
Note
that the first fix is subjective already. For example, the event could be used
to know if a type is [Serializable] or not, even if it doesn't has the attribute. This will
solve the problem for types that have a serializable structure but not the
attribute (and can be even considered a source of bugs if used incorrectly),
but it will not help with types that don't have a valid structure but could be
serialized by an user-made algorithm.
So,
calling the event asking to serialize a type that's not serializable (instead
of trying to consider it [Serializable]) would be much more appropriate. Yet MulticastDelegates
aren't optimized to have a single answer. That is, there could be more than 30
(or even 300) event handlers attached, each one dedicated to a single type.
Should we execute all the handlers all the time?
That
means we may require another solution (well, at least if we want an optimal
solution, as by simply having the event it is already possible to build a
better solution on top of it). Mine solution for the serialization problem is
to try to find a serializer for such a type, and then register such serializer
in a dictionary. That is, I don't ask to serialize a given instance, I ask if
there's a serializer for such a type and, if there is, I know that I can
serialize other instances of the same type without having to call the event
again (yes, I wrote my own serialization framework).
Well,
for the entire concept I wrote an article called Actionless Frameworks, so check that article if you are
interested.
Architecture - Second "Fix"
The
first fix by itself may suffer from another problem: Be too local.
That
is, for the serialization problem we may create a solution where the serializer
has an event to serialize types it is not naturally capable of serializing. But
will we add the handler per instance?
Even
if you think it is appropriate (and it usually is), it is also very important
to avoid repetitions and so, by the same way a type that has the [Serializable] attribute
doesn't need to be "added" as valid per serializer instance, it is
very important to have global solutions.
In
fact, we can say that the first "fix" should exist as a static
solution so it can work globally. If you add local and global solutions or only
global solutions is not that important, as a global solution, if well written
by the user of your code, could work correctly for local situations. The
opposite, unfortunately, isn't true.
Architecture - Services
Now
I will stop focusing on the fact that I like to create frameworks or on the
problems of existing framework as you may be the kind of person that says that
you will not create a framework and you will accept the limitations of the
existing ones.
So
I will talk about SOA (Service Oriented Architecture). It is a common idea now
that we should use SOA as such architecture allows every service to be created
as a separate application, even using different languages if necessary, and
allowing many advantages like distributed processing, real isolation of failure
points and many others.
The
only thing that SOA really requires is communication. And, even if SOA already
means architecture, every service also requires an inner architecture and, at
least in .NET, the most accepted technology to allow the SOA to work is WCF.
Well,
I just complained about WCF being too attribute based, but you may consider it
OK as you will create a new WCF service and implement it as WCF from the start.
So, the fact that it uses WCF specific attributes is not a concern at all...
right?
And
here is where I consider that many applications have a big lack of
architecture.
A
service is created to do some kind of job/solve some kind of problem. Such kind
of solution may work very well as a [web] service. Yet the solution can (and I
dare to say that in most cases it should) exist
independently of the communication framework that's used.
One
of the possible reasons is: Imagine that you decide that for a particular
application you will embed the service in it or even that you will use a
complete different service technology. Wouldn't it be much better if such
"service" is a simple "library", without any WCF specific
data?
So,
the WCF part could be completely stripped away without problems. That is, the
basic architecture may be: "Create any service as a library". Then,
if you want to make it accessible as a real web-service, you create another
application that's bound to the service and only fills the information needed
to expose the library as a service.
And
that's my problem with WCF. While it could be possible to transform a normal
library that's already stateless into a service by simply
"registering" the type as a service (the old and almost obsolete .NET
remoting supports that) in WCF we should have a class full of attributes which,
in a situation like the one I am describing, means it is necessary to have an
"adapter" class per service class, only to add the needed attributes
and redirect to the original, attribute-free, library.
But
the worst problem I see is that many people will simply write all the code
inside the service directly and, if needed, will import the service with a lot
of unnecessary attributes to "embed" a service into an application.
Note: I am not
discussing the fact that WCF can use different transfer protocols and all of
that. I myself created a framework that allows local communications over Memory Mapped Files that's almost 30 times faster
than the best WCF configuration I found for local communication. To me, WCF is
very optimized for remote communications and do great jobs, but it is far from
ideal for local communication, independently of its support for binary
communication and pipes.
Architecture - Program to interfaces - Real
situations
A
common expression that I usually hear and see is that we should "write to
interfaces, not to implementations". This is usually justified for things
like IoC, testing and a lot of "amazing" things.
Yet, as I explained in the IoC topic,
simply making every component talk to others by interfaces is bad. Family of
components expect to work with their relatives.
Yet
a very common situation that I see frequently is people trying to do
globalization by using resources directly. That is, the code is dependent on
the resources API and is not capable of working with non-resource solutions.
I
can go one step further and say that globalization is a kind of feature we
should consider the use of a "framework" or a "service" (or
even both, depending on some kind of configuration).
So,
how can we achieve such a support for both? I just answered that. Interfaces.
Having
all "services" seen as interfaces locally allows those services to be
implemented differently without breaking your code. That is, a basic
application may implement the service to respond that it doesn't find any
translation (and I am already considering the program uses some language, like
English, by default), a little better implementation may use a text file to
find translations, some other implementations may use specific resource files
and some others may redirect to an external service or even find those
translations using a database.
So,
one of the good things that programming to SOA do is that references to other
services are usually already implemented by the use of interfaces. So, if you
have an interface, you can change the actual implementation without problems.
Architecture - Application
When
talking about SOA I said that one of the
advantages is that services usually are presented as interfaces, so the code is
already prepared to be "replaced" by another implementation.
But
that's a half truth. Surely by using interfaces we can replace one instance by
another one. But how are we getting our instances?
A
common architecture problem of SOA consumers is that they call the service
library directly to create the service instances and so, even with interfaces
that allow the implementation to be replaced, they are completely bound to the
technology that implement those interfaces. The code simply can't replace one
implementation by another one, as the "start point" is already the
service library (be it WCF or another one).
So,
following the same principle that we should make our service as a library and
only later, if needed, create the service (as a separate program that uses such
a library) we should program the application in a manner that it doesn't
directly see the communication layer/technology. That is, when you program your
application, it can't ask an instance of the service IMyService to WCF (so, your
application should not see the ChannelFactory, the ClientBase or the System.ServiceModel.dll
directly).
That
is, you can use an IoC container
or you can create your own class that will work as your "factory",
which can use an event to create the implementations to the interfaces
(services) you will ask. Then your code should only use such IoC container or factory as the starting point.
With this extra "layer" you will be able to replace the creation of a
service from a specific library to a "generic" one, and so you will
be able to replace the implementation at any moment (including a local service
instead of a remote one) without breaking all the places that instantiate the
service.
Façades
Now
that I presented the case that to call a service you should not ask directly to
the service library to create the service instances, I will talk about
something that's a little counter-intuitive.
I
was just saying that we should use interfaces so the code can be easily
replaced. But, for many situations, it is better to give some sealed solutions.
Especially when talking about web services, as it is a common practice to pass
all the needed parameters per call.
Compare
this with normal objects that are created, their properties are filled and only
later one or more calls are done, without any parameters or with a very reduced
list of parameters.
So,
to achieve this, we should use façades. We should create local objects that
have a "local approach" to use the services, even if they internally
redirect to one of those interfaces that have many parameters (and to which you
may want to use some default values).
As
I said, this may seem counter-intuitive as I was just saying to program to
interfaces, to avoid adapters and all, and that will be an "adapter"
that uses sealed or even static classes. But that doesn't mean that you
will be bound to an implementation, as such façade will still use your IoC container or configurable factory. This will
only mean that the users will not see the interfaces and the factory all the
time. The developers creating the service and configuring the factory will see
that, but the developers that will only use the service will simply see local
classes that do the work correctly, without having to bother about condensed
method calls and interfaces.
Conclusion
I
hope that after reading this article you can see that home-made (or company
made) frameworks aren't that bad. That worldwide known frameworks aren't
necessarily more prepared to help your application evolve than a framework that
you can write and that you can see that home-made frameworks can benefit from
using actually existing frameworks while keeping the capacity to completely
replace an old external framework by a new one without implying changes to the
application itself, only requiring to fill some adapters if such frameworks
aren't already prepared to adapt to your code.
And
the most important conclusion of all is that if you write a framework, allow
such a framework to be used in applications that already reference other
libraries that aren't going to change, so allow any information that's required
by your code to be found using different methods by creating an event to fill
such an information if it wasn't already given to your framework by other
means.
Reference From
http://www.codeproject.com/Articles/680661/Software-Architecture
By Paulo Zemek
No comments:
Post a Comment