This article provides a summary of the most popular frameworks
currently available for Flex so that you can make the most informed
choice possible regarding which framework best suits the needs of
your team or project. It covers the Cairngorm, Mate, PureMVC, and
Swiz frameworks. I chose these frameworks in particular because they
have been covered by the Flex show podcast and/or have been presented
at conferences such as 360|Flex.
Cairngorm is the oldest and best known of the Flex frameworks. It is actually a micro-architecture—that
is, a collection of design patterns that have proven to work well with
one another. Cairngorm borrows heavily from the world of Java
development and focuses on three key areas: handling user actions,
encapsulating server interactions and business logic, and managing the
state on the client and representing that state in the user interface
Building a project in Cairngorm involves breaking your application
into several packages and extending the Cairngorm classes. Here are the
major sections and classes of a Cairngorm project:
ModelLocator—a singleton that acts as a data
repository—represents the state of the program. The singleton nature of
the class ensures that all your program components are accessing the
ServiceLocator is another singleton that acts a centralized location for storing services such as
HTTPServices. Again, because this class is a singleton, all your program components will be accessing the same services.
- Business logic is encapsulated in command classes that implement the
command pattern. These classes represent the logic that responds to
- Events are handled by the
FrontController class, which
executes the appropriate command class for that event. Each user event
that the program can respond to must be registered with this class along
with its corresponding command class.
- Delegate classes are used as proxies for accessing and responding to remote services.
Cairngorm is well known in the Flex community and, as a project on Adobe's Open Source site,
is well supported and has an active community of developers who
continue to work on it. Also, it borrows proven strategies from the Java
development world and has been successfully used to create many
large-scale projects. Finally, it is well suited for team development,
because it provides a highly structured methodology for creating
applications that allow distribution of tasks.
Perhaps the most common criticism leveled against Cairngorm is that
it requires you to write a lot of classes. In Cairngorm, each event maps
to a command; therefore, you have to write a command class for every
event your program can trigger. Additionally, you must write any other
classes that the command must use, such as delegates. This can quickly
turn into a large number of classes for even a modest-sized application.
Second, because Cairngorm implements its own method of handling
events, it can complicate the built-in Flex event model. It also has
some limitations. Because each event must have its own command class,
you are limited to one responder per event. Also, Cairngorm events do
not bubble, so if you want to notify things higher up the container
hierarchy, you will have to do that yourself.
A third common criticism is the framework's reliance on global
singletons, which can make modularization and unit testing difficult.
Although you can break up the model among several singletons to make
these processes easier, the extra work required can complicate the
The Mate framework
Mate is a tag-based, event-driven framework. Tag based
means that it is implemented entirely in MXML. It is event driven in
that the central focus of the framework is to make it easier to define
who responds to events.
There are only two basic requirements for creating a project using
Mate: You must have one or more events, and you must have an MXML file
called an event map—that is, a simple MXML
file included in the main application file. It defines the events you
want to listen to and how they should be handled. You must have at least
one of these files, but you can use multiple event maps, if necessary.
Mate also implements the idea of dependency injection—sometimes referred to as the Hollywood principle,
or "don't call us, we'll call you." Objects are constructed in such a
way that the data they require is provided to them or injected into the
class. In other words, the classes don't call out to get data ("don't
call us") but rather are passed the data they need ("we'll call you").
Mate promotes loose coupling through its use of dependency injection.
Because components do not rely on global singletons, they are freer to
acts as independent agents. Mate does not keep you from using Flex's
built-in event model, nor does it limit you to a single response for
each event as Cairngorm does. Mate's MXML files and tags are
straightforward and easy to use, and if you get stuck, the documentation
is good and there are plenty of code examples on the site.
Mate is MXML only. So, if you are one of those developers who like
doing everything in Adobe ActionScript classes, you're going to have to
adjust your normal routine. Because Mate does not define much of a
structure for your application, it's left up to you to define. Hence,
you will have to do your own team coordination to ensure that all your
developers are coding in a compatible manner. Finally, if you happen to
work with Adobe LiveCycle Data Services ES, be aware that Mate does not currently handle the data management that LiveCycle Data Services ES offers.
The PureMVC framework
Although it is used for Flex, PureMVC
was not actually designed as a Flex framework. The creator of PureMVC
wanted the framework to be language agnostic. In fact, if you visit the
site, you will see that there are implementations and code examples for a
variety of languages.
PureMVC centers on the Model-View-Controller (MVC) pattern, with the
stated goal of separating a project into model, view, and controller
tiers. These tiers are represented by three singleton classes—
Controller—with a fourth singleton called the Façade
that is designed to facilitate communication among the tiers and act as
a central repository for accessing their public methods.
Much like Cairngorm, creating a project using PureMVC involves
dividing your project into several packages, then implementing your
classes by extending the framework classes. PureMVC has the addition of
Façade class, which acts as the main entry point for the application.
Like Cairngorm, PureMVC is a well-established framework and has a
large and active community supporting it. It is also well suited to team
development, because it provides a well-defined structure for how
applications need to be created, standardizing coding across developers.
Because it relies on singletons, PureMVC is prone to many of the same
criticisms leveled at Cairngorm. It is not specifically a Flex
framework, so it does not take advantage of the features of MXML. Like
Cairngorm, PureMVC has its own method of handling events, and it can
make working with the standard Flex event model more difficult. PureMVC
is a fairly complex framework and has a relatively steep initial
learning curve. Unless your team is familiar with it, training new
employees can increase production time.
Finally, like Cairngorm, the PureMVC framework requires the creation
of many classes, which can increase production time and project size.
The Swiz framework
is an inversion of control (IoC) framework that provides methodologies
for simplifying event handling and asynchronous remote method calls. The
main focus of the Swiz framework is to provide a true MVC paradigm in a
simple, effective manner. Unlike Cairngorm and PureMVC, it specifically
steers clear of imposing Java patterns and does not impose any
predefined folder structure.
Creating a project with Swiz involves telling the Swiz framework
about your application components. At its core, Swiz is a centralized
factory pattern. Components are loaded into this factory through a
utility class called the
BeanLoader. When the application starts, the factory handles the instantiation of your components.
Swiz also provides dependency management through a custom metatag called
Autowire tag is a method of defining dependencies among classes that Swiz then handles for you.
Swiz is simple to use and does not impose a predefined structure onto your project. Through its
dependency-injection system, it—like Mate—promotes loose coupling
between components and manages dependencies for you. Also like Mate,
Swiz uses built-in Flex event handling while providing help in such key
areas as facilitating global event dispatching through the use of an
internally referenced singleton.
Again, like Mate, Swiz does not define much of a structure for your
application—that's left up to you to define. Hence, you will have to do
your own team coordination to ensure that all your developers are coding
in a compatible manner.
Second, because it uses custom metatags, additional steps may be
required to set up a project—for example, setting a few extra compiler
arguments. These steps are not difficult, but it is something that the
Swiz framework requires that the other frameworks don't. The
documentation specifically mentions Flex 2 users, so this may not be an
issue for versions later than Flex version 2.
Making your choice
Although by no means exhaustive, the information provided here in
combination with the resources should be enough for a basic
understanding of the methodologies, strengths, and weaknesses of each
framework. So, how do you go about choosing one of these frameworks over
Perhaps the first question to ask is, do I need one? Flex and MXML
provide a very robust methodology for rapidly building applications. The
reason I held off so long on using a framework is that it seemed to me
that it would require more work to adapt what I was trying to do to fit
the framework's methodology than just using the Flex framework. To me, a
framework should be something that makes tasks easier and increases
productivity—not something I use just because I can or because I think
it makes me a better developer for doing so.
One of the benefits of using a framework is that it standardizes how
things are coded. In other words, if programmer A and programmer B are
working on two parts of the same project using the same framework, you
can be pretty sure that the code they write is compatible. So perhaps
another question you need to ask yourself is, how much structure do you
The frameworks examined here vary a great deal in how much predefined
structure they require. If you're working with a large team, you may
want more structure imposed than if you're working on a project by
yourself. It may be that the hit you take on production time and project
size creating all the classes necessary for one of the more structured
frameworks is offset by facilitation of a team work environment and the
code consistency that the predefined structure provides. In contrast, if
you're the only developer working on a project and you just need
something to make life easier and speed up development, then perhaps you
want to go with one of the frameworks that doesn't impose as much
structure on your project.
So, it would seem that choosing the right framework—or choosing not
to use a framework at all—is really a function of the goals of the
developer and the environment in which the project will be created. The
best advice I can give is to be honest with yourself about what you and
the project require.
This article is written by Jeremy Wischusen and licensed under a Creative Commons Attribution-Noncommercial 3.0 Unported License.
The first question in building an application is "How do I divide it up into packages?". For typical business applications, there seems to be two ways of answering this question.
Package By Feature
Package-by-feature uses packages to reflect the feature set. It places all items related to a single feature (and only that feature) into a single directory/package. This results in packages with high cohesion and high modularity, with minimal coupling between packages. Items that work closely together are placed next to each other. They aren't spread out all over the application. It's also interesting to note that, in some cases, deleting a feature can reduce to a single operation - deleting a directory. (Deletion operations might be thought of as a good test for maximum modularity: an item has maximum modularity only if it can be deleted in a single operation.)
In package-by-feature, the package names correspond to important, high-level aspects of the problem domain. For example, a drug prescription application might have these packages :
and so on...
Within each package are all (or most) items related to that particular feature (and only that feature). For example, the com.app.doctor package might contain these items :
- DoctorAction.java - an action or controller object
- Doctor.java - a Model Object
- DoctorDAO.java - Data Access Object
- database items
- user interface items (perhaps a JSP, in the case of a web app)
It's important to note that a package can contain not just Java code, but other files as well. Indeed, in order for package-by-feature to really work as desired, all items related to a given feature - from user interface, to Java code, to database items - must be placed in a single directory dedicated to that feature (and only that feature).
In some cases, a feature/package will not be used by any other feature in the application. If that's the case, it may be removed simply by deleting the directory. If it is indeed used by some other feature, then its removal will not be as simple as a single delete operation.
That is, the package-by-feature idea does not imply that one package can never use items belonging to other packages. Rather, package-by-feature prefers package-private as the default scope, and increases the scope of an item to public only when needed.
Package By Layer
The competing package-by-layer style is different. In package-by-layer, the highest level packages reflect the various application "layers", instead of features, as in :
Here, each feature has its implementation spread out over multiple directories, over what might be loosely called "implementation categories". Each directory contains items that usually aren't closely related to each other. This results in packages with low cohesion and low modularity, with high coupling between packages. As a result, editing a feature involves editing files across different directories. In addition, deleting a feature can almost never be performed in a single operation.
Recommendation : Use Package By Feature
For typical business applications, the package-by-feature style seems to be the superior of the two :
- Higher Modularity: As mentioned above, only package-by-feature has packages with high cohesion, high modularity, and low coupling between packages.
- Easier Code Navigation: Maintenance programmers need to do a lot less searching for items, since all items needed for a given task are usually in the same directory. Some tools that encourage package-by-layer use package naming conventions to ease the problem of tedious code navigation. However, package-by-feature transcends the need for such conventions in the first place, by greatly reducing the need to navigate between directories.
- Higher Level of Abstraction: Staying at a high level of abstraction is one of programming's guiding principles of lasting value. It makes it easier to think about a problem, and emphasizes fundamental services over implementation details. As a direct benefit of being at a high level of abstraction, the application becomes more self-documenting : the overall size of the application is communicated by the number of packages, and the basic features are communicated by the package names. The fundamental flaw with package-by-layer style, on the other hand, is that it puts implementation details ahead of high level abstractions - which is backwards.
- Separates Both Features and Layers: The package-by-feature style still honors the idea of separating layers, but that separation is implemented using separate classes. The package-by-layer style, on the other hand, implements that separation using both separate classes and separate packages, which does not seem necessary or desirable.
- Minimizes Scope: Minimizing scope is another guiding principle of lasting value. Here, package-by-feature allows some classes to decrease their scope from public to package-private. This is a significant change, and will help to minimize ripple effects. The package-by-layer style, on the other hand, effectively abandons package-private scope, and forces you to implement nearly all items as public. This is a fundamental flaw, since it doesn't allow you to minimize ripple effects by keeping secrets.
- Better Growth Style: In the package-by-feature style, the number of classes within each package remains limited to the items related to a specific feature. If a package becomes too large, it may be refactored in a natural way into two or more packages. The package-by-layer style, on the other hand, is monolithic. As an application grows in size, the number of packages remains roughly the same, while the number of classes in each package will increase without bound.
In this model, there is a dispatcher that determines which worker instance will handle the request based on different policies. The application should best be "stateless" so any worker instance can handle the request.
Scatter and Gather
In this model, the dispatcher multicast the request to all workers of the pool. Each worker will compute a local result and send it back to the dispatcher, who will consolidate them into a single response and then send back to the client.
In this model, the dispatcher will first lookup if the request has been made before and try to find the previous result to return, in order to save the actual execution.
This model also known as "Blackboard"; all workers monitors information from the shared space and contributes partial knowledge back to the blackboard. The information is continuously enriched until a solution is reached.
Pipe and Filter
This model is also known as "Data Flow Programming"; all workers connected by pipes where data is flow across.
The model is targeting batch jobs where disk I/O is the major bottleneck. It use a distributed file system so that disk I/O can be done in parallel.
Bulk Synchronous Parellel
This model is based on lock-step execution across all workers, coordinated by a master. Each worker repeat the following steps until the exit condition is reached, when there is no more active workers.
- Each worker read data from input queue
- Each worker perform local processing based on the read data
- Each worker push local result along its direct connection
This model is based on an intelligent scheduler / orchestrator to schedule ready-to-run tasks (based on a dependency graph) across a clusters of dumb workers.
References to architecture are everywhere: in every article, in every ad. And we take this word for granted. We all seem to understand what it means. But there isn't any wellaccepted definition of software architecture. Are we all understanding the same thing? We gladly accept that software architecture is the design, the structure, or the infrastructure. Many ideas are floating around concerning why and how you design or acquire an architecture and who does it. Here are some of the most common misconceptions about Software Architecture.
Architecture and design are the same thing
Architecture is design, but is not all of the design. Architecture is about making decisions on how the system will be built, it stops at the major abstractions, the major elements - the elements that are structurally important, but also those that have a more lasting impact on the performance, reliability, cost, and adaptability of the system. In constrast, design involves a lot more in order to take the design to implementation. In a word, architecture is the fundamental, architecturally-significant aspects of the design.
Architecture and infrastructure are the same thing
The infrastructure is an integral and important part of the architecture: It is the foundation. Choices of platform, operating systems, middleware, database, and so on, are major architectural choices. Architecture must include the application architecture plus the infrastructure architecture. There is far more to architecture than just the infrastructure. The architects have to consider the whole system, including all applications otherwise an overly narrow view of what architecture is may lead to a very nice infrastructure, but the wrong infrastructure for the problem at hand.
Architecture is just a structure
Architecture is more than just the structural organization of design elements, it also includes the description of how these elements collaborate, as well as why things are the way they are (the rationale). The architecture must describer how the architecture fits into the current business context and must address how it can be developed within the current development context.
Architecture is flat and one blueprint is enough
Architecture is a complex beast; it is many things to many different stakeholders. Using a single blueprint to represent architecture results in an unintelligible semantic mess. Like building architects who have floor plans, elevations, electrical cabling diagrams, and so on, we need multiple blueprints to address different concerns, and to express the separate but interdependent structures that exist in an architecture.
Architecture cannot be measured or validated
Architecture is not just a whiteboard exercise that results in a few interconnected boxes and is then labeled a high-level design. The development of the architecture involves design, implmentation, testing, etc. There are many aspects you can validate by inspection, systematic analysis, simulation, or modelization.
Architecture is Science
If there is a spectrum between art (creative) and science (prescriptive), architecture is somewhere in-between. The architecture problem space is quite large. One of the reasons that architecture cannot yet be considered a science is that there are no good guidelines on how to traverse the solution space, "prune it", and then apply the selected solutions. Architecture is becoming an engineering discipline (it is steadily moving toward the science end of the spectrum). Nevertheless, it is not a strict science because if you put two architect in seperate rooms, give them just the requirements, each will (most probably) come up with a different architecture. However, if you constrain them with a set of architectural patterns, their results will be more similar.
Architecture is an art
Let's not fool ourselves. The artistic, creative part of software architecture is usually very small. Most of what architects do is copy solutions that they know worked in other similar circumstances, and assemble them in different forms and combinations, with modest incremental improvements. It is possible to describe an architectural process that has precise steps and prescribed artifacts, and that takes advantage of heuristics and patterns that are starting to be better understood.
As the software development is maturing, the HLDs and LLDs are widely accepted as an integrated part of software development cycle, no matter what software development methodology we use. Even though the need of having a design is accepted and 30% to 40% of the total development effort is spent on design, it is rare to see effective designs that lay strong foundation for the application development. Very often they fail miserably in providing guidance and clarity for the development teams to start with and as a result the actual implementations are miles apart from the original design. In fact, in many cases the only useful purpose design serves is to complete the list of deliverables for the project for its closure and audits.
Writing effective design is one way to prevent such situations. This document attempts to suggest a step by step approach to develop and deliver effective designs that shall not only be followed and respected by developer community but also play key role in development of stable and robust application.
What is design?
Before we get into the details about writing effective design, it is important that we have the same understanding about what the design is that we are referring to here. Here we are essentially talking about HLDs and LLDs for software development which is an essential part of software development and should ideally be available before the start of development face of the project.
What is an HLD?
- Is a document or/and set of UML artifacts that defines the higher level component view of the system
- Usually includes a high-level architecture diagram depicting the components, interfaces and networks that need to be further specified or developed.
- Mentions every work area briefly, clearly delegating the ownership of more detailed design activity to the concerned LLDs.
- May define high level domain model and interaction diagram which shall be elaborated in LLDs
- Defines boundaries of the system, possible subsystems and establish communication methodologies within various sub-systems
- HLD contains details at macro level and so it cannot be given to programmers as a document for coding
- Documents HLD decisions stating the reason for the design decision. For example using Struts2 framework over Spring MVC.
- Is used by designers or senior developers to create LLDs for their subsystems strictly following the boundaries, interfaces and guidelines provided in HLD
What is an LLD?
- Is used as program specification by the developers to develop the code for the subsystem that the LLD is written for
- Is at the lowest level of abstraction before the code itself and clearly define the methods, their parameters return types and interaction with dependencies
- Contains low level domain model and details each and every property of all domain objects
- May contain separate low level ER diagram representing the tables participating in the LLD
- In most cases uses HLD as the input criteria
- Documents LLDs decisions stating the reason for the design decision. For example using inner class rather than separate class.
Why do we need design?
Why we need HLD?
- It serves as a communication tool that speaks to and for all stakeholders of the project. HLD is responsible for establishing vocabulary for the project that helps in project related communication across designers and developers
- It defines overall architecture and subsystems of the application and fulfills all functional and well non-functional requirements which is most important aspect from customer’s point of view
- It enforces standards by publishing guidelines for services and components development
- It bridges the gap between functional and technical views of the system and brings the two view points together to discuss and close issues that are important for the success of the project.
- Creates lower level abstractions of the requirements for the low level designers to work on by breaking up the requirements into deliverable UCs and features. However, the abstraction level is not low enough to hand it over to developer for coding based on it and that’s why we need LLD.
- Starting point or input criteria for LLD
Why we need LLD?
- Helps in thinking ahead and being proactive for low level development related issue. It is a know fact that the early we find an issue the lesser it costs. LLD provides an opportunity for thinking ahead and being proactive about the problems that might come up at later stages of the development. Such issues can be addressed and fixed early in the design phase resulting in stable code and costing less.
- Finalizing implementation strategy for a project before actually implementing it and hence reducing conflicts in development phase and resulting in faster development
- Providing program specifications to the developers and sparing them for doing what they are best at (coding) rather than confusing them with very high level requirements from requirement documents
- LLD provides further lower level abstractions of the requirements and can be handed over to developers for coding programs based on that. So LLD is the input for the development phase of the project
What is an ineffective design?
It is important for the management to be able to quantify effectiveness of a design. Following are a few signs that a good manager should always be looking at to find out if the designs are effective or not.
Characteristics of an ineffective HLD
Following are some signs for ineffective HLD
- LLDs are inefficient (More about it in later section about LLD)
- There are too much discussions happening among high level designer and low level designers instead of resolving issues via query register. This is a clear hint that something is wrong somewhere.
- HLD’s inefficiencies are felt by the LLD designers and still it is not able to convince them or adapt itself to address these inefficiencies
- HLD does not clearly specifies the high level domain model and high level interaction diagrams for all the subsystems defined
- All subsystems and system boundaries are not specified clearly
- Non functional requirements are not addressed and fulfilled
- In LLDs there are significant deviations from the guidelines and service interaction models established in HLD
- Non technical people cannot understand it
Characteristics of an ineffective LLD
Following are some signs for ineffective LLD
- Its inefficiencies are felt and told by the developers and still it is not able to convince them or adapt itself to address these inefficiencies before it is tool late
- There are significant deviations from the design in the actual implementation
- Developers have to refer to HLD, SRS, UCs and other documents etc to get more clarity on implementation process
- The code is unstable and defect churn rate is high
- The LLD is not being referred for the actual implementation
- Huge refactoring activities are occurring in the code to stabilize it
- It has not been reviewed by the author of HLD on which the LLD is based on
- Development is taking much more then planned effort
Root causes of design problems
Problems with HLD
- Lack of technical and business understanding for HLD: One common cause for inefficient design is the fact that the designers/architects/developers are not competent enough to do that. A good mix of business and technical knowledge is required to come up with good HLD. Even a slightly biased view towards business or technology can disrupt the balance and effectiveness of the HLD
- Technology dominates business: It is important for a designer to keep a view of all functional and non-functional requirements that need to be addressed and not to get carried away by the promises and robustness of new frameworks and patterns. No matter how smart technology, architectures, frameworks and patterns you have used, it is all waste unless it meets customer requirements and satisfaction. Loosing focus on these results in ineffective designs.
- Jazzy UML: The design may or may not have strict UML compliant diagrams as long as it can clearly indicate the intentions of the designer and the design decisions that he has taken. On the contrary the designer at times are more concerned about the UML compliance then the real need of design which leads to jazzy and nicely looking but ineffective designs.
Problems with LLD
- Lack of willingness to do LLD before coding: Now that programming languages are evolving to be at higher level of abstraction and delivery mindset is to deliver fast and quick. Design has taken a back seat. Developers feel that every hour they are not writing code shall result in extra hours of overtime later at the time of delivery. This mindset has taken its toll on the quality of design.
- Experimenting with new concepts and patterns: LLD designers often want to introduce modern design patterns and concepts into there design just for the sake of it. This ends up in unnecessary complexity in LLD for developer to understand. Most of the times a lot of irrelevant information is added in the design which is seldom looked at by the readers
- Less documented information and more Diagrams: Diagrams are great tool for visualizing design but they cannot convey the thought process that the designer has gone through to come up with that design. Diagram might reflect the design decisions that the designer of the LLD has taken but can not explain the motivation behind those decisions. It is important for developers to understand the reason and thinking that has gone into it to avoid conflicts and gain consent.
- One template doesn’t fit all projects: Each software development project is different from the other in some aspects which imply that same information and document structure may not meet all the needs for all the projects. Some thought must go into customizing design template to meet specific needs of a project.
What is an effective design?
It is equally important to for the delivery management to know if the design is effective as it is to know if it is not. It is management’s responsibility to not only improve the inefficiencies but also to encourage and carry forward and continue with the best practices and good design methodologies that are being followed.
Characteristics of an effective HLD
- It is at just right level of abstraction so that it makes sense for all stake holders of the project including developers, designers and business analysts. It should speak for and to all stake holders of the project
- It should clearly state all the functional and nonfunctional requirements of the project along with providing high level solutions for achieving them
- Clearly identify all subsystems of the project and defines clear cut framework for enabling communication within these subsystems
- Describes all the layers of the system and integration points with external and internal services
- It should be able justify all aspects of design decisions taken in the HLD. If any aspect of the design cannot be justified, then it is probably worth reevaluating
- It should document all benefits that results from the design decision
- LLDs built on the effective HLD are effective as well and so is the code built on those LLDs. However, waiting this long to check the effectiveness of an HLD is not a good idea and it may be too late for any corrective actions.
- It clearly documents all assumptions, dependencies and risks for the project and allow project manager a chance to arrange the dependencies, confirm the assumptions and mitigate the risks
Characteristics of an effective LLD
- An effective design is a live document that continues to navigate and control the implementation and development process for complete life cycle of the project. It is not dumped aside after first few references.
- It meets the system requirements in a meaningful way and explains the thought process that has gone into for arriving to the design decisions taken in the design
- Code developed on effective LLD is stable and maintainable and churn rate of defects is not much.
- It is at the lowest level of abstraction and provides sufficient clarity for the developer to start coding right away without having to wait for detailed one to one sessions for starting coding for a LLD
How to write an effective design?
So far we have discussed the characteristics of an effective as well as an ineffective design and also the importance of the design and why do we need it. Now it is time that we should get our hands dirty with the real stuff “Writing effective design”. Here we shall discuss the prerequisites and the step by step approach to create effective HLDs and LLDs for a project.
Prerequisites for an effective HLD
- Baseline Requirements: It is always difficult to aim and hit at a moving target. To come up with an effective HLD you need to have complete control over the scope of your design. You need to have base lined requirements before you start with the design. Obviously, the requirements are going to change sometimes but it is better to have clear agreement on base-lined requirements so that you have the scope of negotiation if the requirements change drastically during or after the design.
- Architecture document: There is a wide misconception that architecture is a part of HLD. However, it should not be that way and in fact there should be a separate architecture document for the project describing at least business architecture, information architecture, data architecture and application architecture of the system. The HLD may include references to that document or some specific part of it to throw more light on some HLD decisions. HLD is meant to talk to and for all stake holders of the project and having all these architecture documented inside HLD makes it meaningless for a section of audience. Also, architecture will overshadow the HLD, reducing and diluting the impact that it should have.
- Design template: Same HLD template may not be able to cater all types of projects as every project has a uniqueness that may require customized solutions. It is responsibility of the high level designer to think through and come up with a HLD and LLD template suitable for the project. These templates should typical have the buy-in from business analysts, low level designers, developers and other stake holders of the project
- Thorough understanding of all these documents: This is the most important point and design must not start without the designer having clear understanding of the documents described above.
- Query Register: All queries and confusions that the designer has about anything mentioned in these documents should be resolved before the start of design and the same should be documented neatly in project specific query register so that analysts, designers and developers can access it if required to clarify or respond to queries. The query register should act as a knowledge base for the project.
Writing effective design
- Scope of design is often considered as mere formality and is filled up as last part of the design and is mostly copied from the similar designs that are already available. Some minor tweaks here and there in the copied scope and is considered done. However, this is where we are risking the entire foundation of the project.
- For HLDs scope section should clearly list down the project specific feature scope and it should be in agreement with the business analysts. Break down the requirements in to deliverable UCs and if required split the complex uses cases into independently manageable features for better traceability.
- High level designer should not only be clear about the scope for HLD but should also have a clear view about the breakups and scope for the LLDs that need to be developed based on the HLD. He should have the clear release plan for the LLDs and corresponding implementations.
- High level domain model should define the most important domain objects and the relationships among them. This is an important step as in most cases and with the modern persistence frameworks being used, this part is going to define your database which will eventually become the backbone of your application. Effective domain modeling is must for an effective HLD.
- First step for the designer is to identify the domain objects or entities for you project. For this a common way is to do it by reading the requirements and list down all the nouns that appear in the requirement. This is followed by elimination of duplicates and visibly irrelevant ones from the list. After this step is performed you should be ready with the first draft of domain objects. At this point it is important to get these objects verified from the domain expert. In fact ideally domain expert should be part of the domain object identification session.
- Once domain objects are identified, you would need to think about the relationships between them. At this point it is probably ok to draw your first class diagram which is the high level domain model. The domain model can be at a higher level specifying associations and dependency relationships between domain objects. It should be attached with some relevant notes for low level designers about how and what to elaborate in it in their LLDs
- Components or sub-system identification is the next important activity that the designer has to perform for effective HLD. This is a brain storming exercise that must be done by the high level designers. After this activity is done the designer should have clear view of various subsystems and reusable components of the application and probably also have some idea about how the communication shall work within these components.
- Once all internal and external subsystems and components are identified, the designer has to think through the integration issues and challenges for enabling smooth and effective communication among these systems and at this point should draw the component diagram for the application. How a component shall be reused should be decided at this point.
- High level service model is the next step which should be performed and this step varies depending upon various factors related to development methodologies, technologies, frameworks, project domain and other project specific parameters.
- After this step is complete you should have identified higher level components of your service layer. Service layer image should be drawn in your mind that you would need to resolve specific queries from the low level designers.
- Identify subsystems and integration points may be the next in the HLD. This activity normally goes on throughout the development of your HLD. However it is important to list the entire internal and external component that you can think of. The list can be refined and polished as the HLD progresses.
- At this point you should plot the component diagram for your project clearly specifying all subsystems internal and external and the interaction between them.
- Provide realization of identified UCs in the scope section. After all the above steps are done you would need to provide the high level realization for your UCs. For measuring the real progress and completion status of the development it may be required to introduce the concept of features.
- One feature may be involved in the realization of several UCs
- Full realization of a UC requires the implementation of all supporting features.
- At this point high level designer would need to provide some interaction diagrams like sequence diagram or collaboration diagrams for each feature and UC. These diagrams may be at a very higher level indicating the message flow among various objects across various layers of the application and providing clear integration points that should be further elaborated on in LLDs.
- High level designer may have to discuss the integration points and strategies with the architecture team to ensure that it fits in the overall integration architecture of the system. Also designer has to ensure that architecture guidelines are followed and the HLD doesn’t violate any aspect of architecture. That is why it was mentioned that the high level designer should thoroughly understand the application architecture.
Prerequisites for an effective LLD
Apart from all the prerequisites for HLD, the LLD designer should ensure that following artifacts are available before he/she starts with the LLD
- HLD is the most important artifact that the low level designer should have with him before he starts with the LLDs. HLD establishes a contract and vocabulary that needs to be respected by the LLD and implementation code.
- High level scope for the LLD. This should typically be provided by the high level designer who has the complete visibility about entire project and all LLD developments that may be going on in parallel. High level designer may have decided to break the functionality defined in the HLD in multiple LLDs and may have some planning about each LLD release. LLD designer must have the clear understanding about the things that are in scope of his LLD and also the overall release plan so that he can align his priorities with the project priorities.
- Query register should be in place while the HLD was being created and should have query resolutions from the HLD phase. LLD designer should have access to this knowledge base so that he has clarity on business related discussions that might have happened in HLD phase and that are logged in there.
Writing effective LLD
- Designer should first have complete clarity on scope of his LLD. He should analyze the scope provided by the high level designer and if required break it up further into some lower level scope items.
- Low level domain model is the next thing that the designer has to create.
- For this the designer should identify all properties and behaviors for each domain object and it should be filled in each entity in the low level class diagram.
- Designer should also identify other domain objects that may not have been identified in HLD but may be required in LLD for its implementation.
- Low level designer should name domain object as per project specific naming convention and should also name all the relationships between domain object along with the multiplicity of the relationships. This step is required for the developer to come up with a stable entity relationship model quickly.
- Low level domain model should be at as lower level as possible to the code and should have one to one mapping with entities that are created in the code. Don’t expect and allow the developer to think and apply there thoughts in this area as domain model is the back bone of your application which you cannot afford to expose to the developer to mess around with.
- Realization of the UCs and the features is the next step for the LLD designer. Designer has to work closely with the developers and should be aware of the lower level APIs of the code. It is important for the designer to have this knowledge to come up with design that is closer to the reality.
- First step here is to study the higher level interaction diagram that HLD designer has provided to understand the integration points with internal and external components and interaction between various layer of the system
- Next step if to do some lower level detailing for each interaction defined. For example the HLD may only mention that your service needs to talk to some external LDAP service for authentication. The low lever designer should however provide the complete flow that realizes that interaction. This would include exact class names, method name, parameters and return type.
- So the high level sequence diagram may have only important participating component but the lower level sequence diagrams should have all the participating objects for that flow at lowest level of abstraction.
- Each method call should be clearly defined in the interaction diagram giving no way to any confusion for the developer. At this point the designer has to see his diagrams from developer’s perspective and scrutinize it for lack of information. All findings should be corrected.
- Document all the design decisions in the notes within the sequence diagrams. It is better to document all the design decisions, assumptions and dependencies related to an interaction diagram in a note within that diagram itself rather then putting these all these in one section of the LLD. This helps the developer in relating to the thought process of the LLD designer while designing that flow.
- If required it is also a good practice to provide pseudo code for complex part of an interaction diagram. More information you provide for your flows, lesser thinking developers have to do. It is a known fact the earlier you think of a problem better control you have on them. So it is worth thinking and writing pseudo code for complex part of the interaction in the LLD itself.
- Please don’t experiment with new design patterns or fancy stuff just for the sake of it and stick to what is known to work with your project. It doesn’t mean that you should kill your ideas and creativity. All I am trying to convey here is that to be pragmatic and ensure that you have done POCs before you are introducing something new in your design. Putting things in design first and then doing the POCs for them is not a good idea and is a huge risk for your project.
- The important skill that designer need to develop is to have a flexible and open mindset. He should be able to think from perspective of an analyst and a developer, both at the same time and need to maintain a fine balance between them for the benefit of project.
- Without complete clarity about the domain it is nearly impossible to come up with effective design for applications that are meant to work on that domain.
- It certainly helps the designer if he has good command over UML as it helps creating accurate interaction diagrams quickly. However, it is still possible to come up with good design even if the designer in not an expert at UML but has an analytical mindset and can clarify his intentions and design decisions in the design document.
- High level domain model, components of the system, clear and justified design decisions, assumptions, dependencies and risks for the project are some important things that must be included in the HLD.
- Low level designs should be at lowest level of abstraction and should not allow the developer to think and take decisions that are critical for the application. All these decisions should be taken while developing the LLD.
- All POCs should be done prior to suggesting new design patterns, technologies and approaches that the developers are not familiar with. Suggesting first and doing later may be a risk for the project and is a big NO.
What is Refactoring anyways?
Refactoring is the process of changing a computer program's source code without modifying its external functional behavior in order to improve some of the non-functional attributes of the software. It is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behaviour. Advantages include improved code readability and reduced complexity to improve the maintainability of the source code, as well as a more expressive internal architecture or object model to improve extensibility.
Its heart is a series of small behavior preserving transformations. Each transformation (called a 'refactoring') does little, but a sequence of transformations can produce a significant restructuring. Since each refactoring is small, it's less likely to go wrong. The system is also kept fully working after each small refactoring, reducing the chances that a system can get seriously broken during the restructuring. By continuously improving the design of code, we make it easier and easier to work with. If you get into the hygienic habit of refactoring continuously, you'll find that it is easier to extend and maintain code.
Motivation for Refactoring
Refactoring is usually motivated by noticing a code smell. For example the method at hand may be very long, or it may be a near duplicate of another nearby method. Once recognized, such problems can be addressed by refactoring the source code, or transforming it into a new form that behaves the same as before but that no longer "smells". For a long routine, extract one or more smaller subroutines. Or for duplicate routines, remove the duplication and utilize one shared function in their place. Failure to perform refactoring can result in accumulating technical debt. There are two general categories of benefits to the activity of refactoring.
- Maintainability. It is easier to fix bugs because the source code is easy to read and the intent of its author is easy to grasp. This might be achieved by reducing large monolithic routines into a set of individually concise, well-named, single-purpose methods. It might be achieved by moving a method to a more appropriate class, or by removing misleading comments.
- Extensibility. It is easier to extend the capabilities of the application if it uses recognizable design patterns, and it provides some flexibility where none before may have existed.
Importance of Unit Tests
Before refactoring a section of code, a solid set of automatic unit tests is needed. The tests should demonstrate in a few seconds that the behaviour of the module is correct. The process is then an iterative cycle of making a small program transformation, testing it to ensure correctness, and making another small transformation. If at any point a test fails, you undo your last small change and try again in a different way. Through many small steps the program moves from where it was to where you want it to be. Proponents of extreme programming and other agile methodologies describe this activity as an integral part of the software development cycle.
List of refactoring techniques
Here is a very incomplete list of code refactorings. A longer list can be found in Fowler's Refactoring book and on Fowler's Refactoring Website
Techniques that allow for more abstraction
- Encapsulate Field – force code to access the field with getter and setter methods
- Generalize Type – create more general types to allow for more code sharing
- Replace type-checking code with State/Strategy
- Replace conditional with polymorphism
Techniques for breaking code apart into more logical pieces
- Extract Method, to turn part of a larger method into a new method. By breaking down code in smaller pieces, it is more easily understandable. This is also applicable to functions.
- Extract Class moves part of the code from an existing class into a new class.
Techniques for improving names and location of code
- Move Method or Move Field – move to a more appropriate Class or source file
- Rename Method or Rename Field – changing the name into a new one that better reveals its purpose
- Pull Up – in OOP, move to a superclass
- Push Down – in OOP, move to a subclass