Showing posts with label software architecture. Show all posts
Showing posts with label software architecture. Show all posts

Wednesday, August 19, 2009

Domain-Driven Design, Services, and Data Access

The term Domain-Driven Design, or DDD, has long become a buzz phrase in the software community – after it was coined and popularized by Eric Evans in his book “Domain-Driven Design: Tackling Complexity in the Heart of Software”. The book does an excellent job summarizing the essential principles of object-oriented software design based on domain modeling, stressing the importance of strategic design, refactoring, etc., as well as documenting various practical design patterns (some of which I like better than the others, by the way.) It is fair to say that long before the book first came out in 2004 and the DDD term was coined, the importance and necessity of proper modeling of software systems based on distinct clearly defined functional domains was well understood among the better software engineers. However, the well-written and intelligently organized book on the subject was immediately embraced as a consolidated reference and effective tool in evangelizing the concepts of good design.

To represent a system in a clear and effective way, a model should draw a clean distinction between its artifacts based on their roles and functional domains they relate to, as well as accurately define the relationships between such artifacts and between the domains themselves. It is not only important and extremely useful to distinguish the model artifacts by their roles and purposes, but to group them by the functional domains.

Today, few people argue the benefits of the DDD principles. As DDD has gained wide acceptance and recognition, it inevitably became a buzz phrase. With that came some unwanted side effects.

Like any sophisticated and effective design methodology, DDD has not been born out of nothing. It has its prerequisites. Understanding of DDD requires familiarity and deep understanding of the programming fundamentals, structured design, object-oriented principles, modularity, re-factoring, etc. More than anything, to appreciate and benefit from DDD the programmer must truly understand the dangers of excessive complexity of software, and feel the sincere need to avoid such complexity by applying intelligent design and creative thinking. In other words, mechanical following the patterns listed in a book will lead you nowhere. Your heart and mind must be in it!

Lately, I have been reading or hearing comments similar to this: “Oh, we don't use services; we use DDD.” That usually comes with an implication that “services” are so last season...

Hmmm... Why? Is such point of view based on only a superficial familiarity with DDD (perhaps, limited to a second-hand familiarity with one or two of the design patterns from the Evans book?) Or is it, perhaps, based on a misinterpretation of Martin Fowler's article in which Fowler talks about the flaws of the anemic domain model? In the anemic model, domain objects are reduced to all but data holders with getters and setters, while procedural services implement all the behavior applicable to the domain objects. Fowler – rightfully so – dismisses such approach as an anti-pattern and a quite incompetent [mis]use of objects. I whole-heartedly agree with him, and anyone who thinks that objects in a well-designed model should encapsulate both data and behavior. In the context of a Domain Model, this means that domain entities must implement domain-entity-specific logic that belongs on those entities, the logic essential to the definition of the nature of the entity, the logic without which no instance of the entity would make complete sense. Such logic must include the implementation of the ways the entity manages its own data as well as the relationships with other domain entities that the entity instances directly rely on.

The logic inside an entity class should not, however, include anything that is completely foreign to the domain model in question, e.g. the business logic specific to a particular application. That includes any data access logic. Such logic, according to Eric Evans, should live in the Application Layer, or... services.

In other words, domain entities should contain as much logic as possible, but no more than that!

However let's take a look at how some programmers (and architects!) interpret this seemingly simple and obvious concept. It is not uncommon to see application logic and data access logic wired directly into the domain entity classes. Needless to say, this makes the domain objects rigid, hard-wired for a specific application, and often all but useless when new business scenarios come up – even within the same enterprise. Have these folks, perhaps, skipped the “Introduction to Structural Programming” and “Object-Oriented Programming, Part I” classes on their way to becoming Sr. Software Engineers?

Embedding any notion of persistence (be it self-persisting methods or a reference to some object repository) is not my idea of a good design pattern, by any means. (This is where I might disagree with Eric Evans, and many others, but I am sticking to my guns on this subject, and please don't crucify me for this.)

Just the fact that an instance of A may under certain circumstances participate in scenario B, does not mean that the logic for B must be forever burnt into A making the two inseparable. The basic rules of software design and common sense suggest that we must always do our best to separate and de-couple things that do not need to be molded together. Not the other way around!

Naturally, one of the most essential steps in object modeling is to properly identify which data and which behavior actually belong on each given object/class. That is not always a trivial task by any means. I may sound as if I am talking to my 3-year-old when I repeat the same thing over and over: not all functionality that may be applied to a class must live on that class. It seems such an obvious and easy to understand point. But why do programmers continue to stuff application logic into their domain entities? And, may I note, very proudly so. I have lost count of the self-proclaimed DDD nouveau experts whose idea of good OO design is consolidating all (thinkable and unthinkable) business logic inside a domain entity.

When asked, they always point at the Evans DDD book. As if the book really promotes such nonsense.

Have they really read and understood the book? Doesn't Eric Evans specifically state that any application logic should live in the Application Layer, or … er... application services? Amazingly, this simple idea is overlooked by the zealots who proudly state that they "use DDD, and not services."

All this only proves to me how dangerous any tool, methodology, or teaching can be in the hands of those who have no patience or desire to actually learn it and understand the principles behind it. Instead, many people prefer to skim the surface and stop as soon as they come across something that seems like a quick solution to their immediate problem. Unfortunately, such attitude is very common in the software industry today, in general. It is common for programmers to search for easy, ready-to-use solutions on the internet, grab the first one that looks like it does the job, then cut and paste it into their applications without understanding how it works. And it may not. I have seen that too often...

De-coupling the application logic from domain logic provides possibilities to use the same domain models in various contexts and applications, without having to produce duplicates, without creating more work and hurdles. Any functionality that does not conceptually belong on the domain entity should live on a different type of object.

Generally speaking, use-case-specific operations must live inside the object that implements the use case. It does not matter how you call such objects. Personally, I see no problem with calling such objects “services.” If you don't like the word or think it has been stigmatized, feel free to call it something else. Just don't put your specific application's logic into a true domain entity class.

Use cases are normally specific to a particular application, not a generic domain.

Domain models - generally - should be designed to be useful for more than one particular application. At the very least, they should be re-usable by potentially multiple applications within the enterprise, if appropriate, or multiple portlets within a portal, etc. One generic thing that a Domain may helpfully provide for the applications to implement is the API for domain service objects - whenever appropriate, of course.


Functional Domains as Reusable Components

It is convenient and very effective to consolidate each distinctive functional domain inside a dedicated component. This means that all software artifacts for the given domain are physically grouped together. In terms of Java packages, this suggests that all classes for the given domain are packaged under the same root package. Applications may use these domain packages as JAR dependencies, and, if necessary, provide their own, application-specific implementations of the domain service APIs (if the domain model provides such API), including the application-specific data access detail, – in their “application layers.”

People like to say that they don't believe in re-use, that any new project requires writing brand new classes from scratch anyway, etc. I categorically disagree with such philosophy. I can't tell you how many times I was able to benefit from re-using generic domain components I had written – by using them on more than one application. The key here is thoughtful design and getting things done well the first time around. There is no point in trying to re-use a poorly written rigid and not adoptive piece of code... That's why I believe that getting things well really pays off in a long run. Of course, as time goes by, I find myself making adjustments and improvements to such components, but that is called normal re-factorings.

A Domain Model normally consists of the following types of domain artifacts:

  • Domain entities; those are subjects and actors in the given domain; Domain entity objects must have no knowledge of any application-specific functionality whatsoever. They should not define any persistence logic, nor should they store any direct references to any type of objects that implement data access. For example, some ''UsZipCode'' class may implement such behavior as ZIP validation, parsing of the input data, splitting the ZIP into a 5-digit code, 4-digit extension, or representing itself in several different ways: 5-, 9-, or 11-digit code, etc. All of such functionality is essential to defining the very concept of a US Zip Code. However, it has nothing to do with the world outside the class itself. Nothing in such ZIP Code class depends on, or makes assumptions about, how the objects of the class will be used in applications. Entity definitions must be de-coupled from any external operations that may be performed on the entity instances. The necessity in such operations (use cases) may come and go, but the domain entity objects will remain what they are - regardless of how they are used. It is absolutely valid to expose domain entity objects to the presentation tier (e.g. controllers, form beans, action forms, etc.) and DAOs. If necessary, instances of domain entities may and should be passed between the presentation tier classes and application services in the middle-tier.

  • Domain Services (APIs): define the operations that do not conceptually belong on domain entities; these may be generic domain-specific use cases/scenarios that may be applicable to the domain entities but may not be considered the integral part of the essential behavior of any particular domain entity; these use cases define the context in which some instances of the domain entities may be used and any operations applicable to these entities; the actual implementations of these service APIs may be application specific. The clients of the services may be application presentation tiers, external applications (i.e. web applications, batch processes, web services, etc.) or other services. A ''service'' module exposes only the ''use cases'' the service implements – via its public API/Domain Model. Everything else, including any data access logic and technology, should be considered the implementation details of the service that are not directly exposed to the clients. Use cases implemented by services must reflect logical operations and never expose a notion of any particular data store. For example, a client that submits a product order should only be aware of the mere placing of the order, with no indications of what the underlying system actually does with the order, whether the data is stored in the database, etc. As Eric Evans and martin Fowler both point out, the application services would normally be fairly light-weight and abstracting the minimum application-specific logic and persistence, if necessary. It is absolutely fine for such services to do little more than forwarding to the underlying DAOs. That is a very small price to pay for flexibility, ability to swap implementations, and future maintenance.

  • Domain Factories that produce instances of domain objects;

  • Domain Value Objects, if necessary; I can't say I often find much use for pure value objects, however.

A domain-specific component abstracts any implementation details of the functionality it provides while exposing the operations via the ''public interfaces''. In some cases, it may not even have to provide the implementations at all – leaving that task up to its clients. In the terms of a programming language such as Java, the Application Programming Interfaces (APIs) of such components are defined as methods that represent the business operations of the given functional domain. These methods may live on the entities themselves, if appropriate, or on domain services if such services are applicable to the given domain. The domain entity objects may be returned by and/or accepted as the arguments of such interface methods.

Domain components may use other components and 3rd party libraries as their dependencies. Each component is developed, maintained, and distributed independently of any client applications or other components that may rely on their functionality.


Data Access

Some use cases implemented by a service or component may require access to data resources such as databases, in-memory data, file systems, web services, or other types of remote or local data sources.

I whole-heartedly believe that a data access operation should not be considered a use case in its own right outside the context of the business operation that requires that data access in order to complete. It should always be abstracted by a service that implements the use cases.

For example, some Order service may expose the "Place Order" Use Case API that reveals nothing about how and where, or whether, the order should be saved. It only tells the client that the order will be processed and the client can expect the result promised by the API. Once the client calls this generic API, the actual service implementation may (or may not) need to ask another service (e.g. some Product Service) to check whether the given product is available, etc., and then invoke its data access module to save the order in the data store defined by the service configuration. The Order service is agnostic of the Product service’s data access implementation and is only aware of the Product service’s public interface, e.g. the API that implements the “Check Product Availability” use case. The client, in this particular case, is not aware of any communication between the Order service and the Product service. The Order service API, in this example, serves as a single-entry façade for the clients who need to place orders. Simple.

A data access object (DAO) is a Java class (POJO, of course) that abstracts any data access logic and data source for the given service. DAOs are not limited to implementing access to databases. They may abstract any kind of data sources including in-memory data, files, access to data via web services, etc.

I do not share the preference of some architects that DAOs should be designed on the one-per-entity or one-per-table basis. Such approach, in my view, has at least two major flaws. First, it produces extra complexity due to a large number of finely grained DAO/repository classes. Second, it usually exposes the notion of persistence to the entities. A service, on the other hand, provides a single logical entry per use case while abstracting any DAOs/repositories inside and normally grouping data access functionality by the use-case relevance, not by entity type. This way, one may get by with only one or two DAOs per service vs. one per each entity type. Since data sources and the data access details are usually specific to a particular application, I lean towards providing application-specific implementations of DAOs (one or several) per each domain/application service.

Generally, a service may abstract a single DAO or a set of dedicated DAOs. Any given DAO may only be used by one service and not exposed to multiple services. Since data access operations are nothing more than implementation details of a more generic use cases represented by the service, no other service or application should ever need to access the DAOs directly. Instead, they must talk to the APIs of the service that wraps the DAO(s.)


The above are my personal views and recommendations that I hope the reader finds helpful and comprehensive. I don't have a goal of converting everyone to my point of view. These approaches to multi-tier architecture and domain modeling, however, are shared by many architects, and have proven quite effective. There is no single good way to solve every problem. Needless to say, variations and alternatives to the approaches described here are relevant and often desirable. The general goal however should never change: keep things simple and clean, as much as possible.

Tuesday, August 11, 2009

The Art and Craft of Software Engineering

With all the exciting new technologies, powerful intelligent tools and frameworks popping out almost every day, have we, perhaps, missed one strange and scary turn the software industry has taken at some point in the past years? Ask a typical hiring manager about what they are looking for in a software engineer, architect, programmer. You will typically be presented with a long list of technologies, methodologies, frameworks, and just plain flavor-of-the-day buzzwords that the manager is dying to see on the applicant's resume. Everybody is talking about Agile development, Scrum, fast-paced environments, etc. There would be nothing wrong with that - if one simple requirement and expectation had not been all but lost behind all these buzzwords. How about the basic talent for software craftsmanship? Since when does the familiarity with specific technologies and processes (often quite superficial, by the way) replace the creative vision and true ability to design and code good quality software? One may argue that the latter is always implied and is such an essential requirement that it would be silly even to mention it! I might believe that if I hadn't seen so many software engineers who couldn't design or write decent code even if their lives depended on that! Oh, and, by the way, all those folks happily and proudly claim having worked with the latest and the greatest technologies and tools. It is only because of what I have seen with my own eyes on so many projects, I present you with the rant that follows... ;)

It should not be a secret to anyone that any tool, technology, or process may be misused by an incompetent person. An incompetent programmer will continue to write bad code even if he or she uses a brilliant framework such as Spring. There is no magic! No matter how many meetings the team has each day, how many status reports they produce, how much their managers talk about agile processes and SCRUM, the project will still be a chaotic mess if there are no good programmers on the team.

Any tool, technology, or process is only as good as the people who use it.

In today's sea of programmers, architects, and all sorts of people who call themselves "software professionals", how many are indeed good, reliable Software Engineers capable of producing good quality software?

There is a common opinion that suggests that the usual “80-20” rule is applicable to the software community as well. Some people, however, are less forgiving. The brilliant Edsger W. Dijkstra - considered by many the Father of Computing Science - once suggested that only 10% of all software engineers are any good at all. The outspoken author and software professional Allen Holub in his brilliant article “The Terror of Code in the Wrong Hands” goes even further and suggests that only about 5% - the so-called elite programmers - can be trusted to get the job done without causing various degrees of harm. So, what is the magic ingredient that distinguishes a good software engineer from everybody else?

Wired for Intellectual Manageability

The most important quality of a true software engineer – in my opinion – is the ability (talent and vision) to effectively represent any complex problem as a set of simple, intellectually manageable parts where each of those parts can be viewed, analyzed, and worked on individually without appearing overwhelmingly complex. This involves the ability to identify, separate, and abstract distinct functional domains, concerns, and concepts within the subject system – however large or small that system itself is. The subject may be a high-level architectural view, or a single low-level operation where distinct groups of micro-steps may be identified and abstracted into smaller and more manageable subroutines. Regardless of the flavor-of-the-day technology or design methodology used, any approach absolutely must focus on de-coupling things that do not have to be - and should not be - hard-wired to one another.

What is even more important is that a good Software Engineer understands - and constantly feels - the necessity to maintain such separation and intellectual manageability of individual parts at any given moment of the development process!

Refactoring is not a buzzword. It is not a separate project scheduled for later either. It is the way of programming, a vital integral part of the development process - every single minute of it. Good programmers refactor subconsciously with almost every line of code they write - not because they want to keep things pretty but because their brains are wired to keep things well organized and intellectually manageable at any given moment. That is the key to crafting software that is always in the stable working condition. That is the only way agile development can really work!

In a reasonable amount of time any new technology or process may be learnt and adopted by anyone with sufficient general intelligence. Unfortunately, not everyone has the vision and ability to model well. Just like the gift of playing a musical instrument or creating works of art, that skill may only be acquired with practice by those who were born with the very specific talent for it. I am absolutely convinced that software engineering requires a unique combination of intelligence, scientific mindset, and artistic vision. Different people are born with different talents and inclinations, and those three qualities are absolutely essential for being a true Software Engineer. None of these three ingredients may be aquired with time: you either have it, or you don't.

A true Software Engineer, without a doubt, is an artist and a craftsman. And with that comes elegance, efficiency, fast results, manageability, and high quality of the software he or she designs.

I believe that creative artistic vision is especially essential in software modeling. The ability of the software engineer to visualize distinct concepts and their relationships, represent them in a clear, elegant, and non-convoluted model – that is what makes the difference between quick results and an endless stressful nightmare, between making profit and loosing money, between the success and failure. When I talk about a “model” or “design” I don't necessarily mean a “design document”, a diagram, or a particular stage of the software development process. I am talking about any type of representation of the subject/problem, including – and foremost – mental visualization, at any given stage of the creative process of designing software. The ability of such visualization and modeling is crucial at any given moment, and at any given level – whether one is working on a high-level architecture or a single low-level method or function. That is the skill that allows the better developers to properly abstract the complexity in components, modules, classes, and subroutines, ensuring intellectual manageability of every individual piece. On the other hand, those who lack that essential ability, end up writing excessively complex, convoluted, hard-to-manage, hard-to-maintain, and very costly software.

Invest in Competence: Why Is It So Difficult?

Building software is not trivial, by any means. Software engineers must deal with many complex issues at a time, overwhelming schedules, confusing and ever-changing business requirements. So why do companies continue to make things even more difficult and stressful by hiring mediocre engineers who are simply not capable of designing and coding well? Why is convoluted spaghetti code not only tolerated but is literally a norm on so many critical projects today?

Building a competent development team requires first of all a very competent technical manager who can identify a good software engineer in the sea of fly-bys and imposters. Such manager must also have the freedom and means to offer good compensation and motivation to the people he or she hires. The manager must also have the freedom to easily let go of those team members who are not performing well, show no improvements or desire to improve. When it comes to offering compensation packages, more often than not, the managers are limited to working with what is given to them by the HR departments or other managers. Unfortunately, even if the company itself can afford paying their employees well, among the people who actually determine the salary/rate caps for software engineers not everyone knows and appreciates the difference between a good software engineer and an "average" one. And, tragically, most non-software folks can't even imagine in their worst nightmare that the fine line between an "average" SE and a really good one in practice translates into a monstrous difference between a complete failure and a huge success. As the result, companies "save" pennies on salaries and waste millions of dollars on disastrous projects that never end.

Although unwillingness of many companies to invest into truly high-quality engineers is a serious problem, it may be - at least, partially, - overcome by a dedication of a competent technical manager who finds other ways to interest and keep highly-skilled top-notch resources. Such managers understand that salary, however important, is not the only motivational factor for a good SE. And this is why it is important to remember that true Software Engineers are artists who take pride in their craft and draw satisfaction from their daily work. For a true software engineer, interesting work on a team with like-minded people who are equally passionate about the common cause is, at the very least, as important as the salary. A good software engineer will think twice before turning down an interesting, creatively challenging assignment. On the other hand, such people are unlikely to stick around long if they find themselves surrounded by lazy unmotivated slouches and drones. Imagine an artist coming back to work each day only to find that some thoughtless fool has painted vulgarities over his masterpiece! Day after day! That is exactly how a good programmer often feels on the team with so called "average software engineers."

A more prosaic and, unfortunately, very common reason why so many teams consist of poor programmers is - sadly - the incompetence and personal insecurities of some hiring managers. This is, of course, true for any industry, and has to do with the human nature. While a good manager tries to hire the best people and let them do what they do best, an incompetent and insecure manager is more concerned with preserving his or her perceived status in the company. Therefore such folks naturally tend to surround themselves with the kind of employees who are not likely to challenge them in any way, who will not expose their weaknesses. It is not uncommon for a good software engineer who's passionate about his/her work to outspoken. Some managers don't like that and may act very annoyed and "concerned" when an "ordinary" engineer speaks up in meetings with new ideas or suggestions to improve things. Go to any popular online software forum, and you are all but guaranteed to come across desperate outbursts and bitter stories about managers or "chief architects" that suppress creativity or enforce old-fashioned dogmas on their teams - just because they themselves don't know any better.

Unfortunately, a truly good development manager is just as hard to come by as a good programmer. Due to the unfortunate lack of appreciation for the value of a good software engineer, most organizations pay the best of their SEs significantly less than they pay development managers. If a SE wants to earn more, their only way - within the given organization - is to grow first into a position of an "Architect", and then into a "Development Manager".

Generally, I believe that the only major difference between an "Architect" and a "Programmer"is that a true architect is expected to have knowledge of a broader spectrum of platforms and technologies, the ability to choose and integrate the right ones, while a "programmer" may get away with simply focusing on a narrower set within a project. Both should be equally capable of understanding the principles of software design and programming. I am convinced that a poor programmer may never become a good software architect. And a truly good software architect is always a good programmer. Architecture and design are not limited to the high-level view. In software, every single artifact, however small, requires thoughtful design and architectural vision. In many organizations, however, the title of an Architect relates to a semi-bureaucratic position somewhere between the enginnering team and the management - usually, closer to the management. "Architects" in such organizations spend most of their time in meetings with the managers and business people. They rarely work closely with programmers, and never write code themselves. However, since such positions usually come with higher salaries, good programmers face the dilemma: to move away from programming into a managerial position, or to look for a way out of that company in favor of something better. Some choose to become free-lance consultants just so that they can continue to do what they do best and what they love, while getting paid fairly well without competing for titles with more political types. It is not a secret that brilliant software engineers not always have great managerial skills. If a good programmer becomes a manager, the organization often loses a good programmer and - quite possibly - acquires a mediocre manager. Some of the good engineers, indeed, make for excellent engineering managers. I am fortunate to have worked with such people. Some, however, become lousy managers - just as before they were sub-par programmers.

IN my opinion, for things to improve, the companies (and I mean, the top management and HR) must realize and admit the following:

  • not everyone who calls himself/herself a Software Engineer actually is one; quite a few of such people do more harm than good by creating a false impression that the project is in full swing, while their incompetent actions are actually leading it away from the successful completion; if there's no one to identify such people on the project, the management may never know that the job that takes over a year could have been completed in a couple of months by just one or two real software engineers.
  • one good software engineer can do more work - faster and better - than a team of (sometimes 10 or 20!) "average" software engineers; therefore, hiring only good software engineers would allow to significantly reduce the sizes of teams and departments and get the jobs done faster and better;
  • good software engineers are the ones who do all the work and are the decisive factor in ensuring the success of theh project;
  • it is okay to pay good SEs at least as much as managers - based on the value they bring and uniqueness of their skills; I have witnessed a situation when the hiring manager's ego stood in the way of hiring an experienced consultant only because the manager could not live with the fact that the consultant's hourly rate - if multiplied by 40 hours and 52 weeks - amounted to a yearly income that apparently was higher than the manager's salary (the manager in question had actually openly voiced that concern); managers often claim that they can't find good people for the job, but what they are often not saying is that they can't find good people for the kind of salary they offer...
  • and finally, it should be made easier for companies to fire those who consistently underperform or demonstrate incompetence and inability to create good software - in order to avoid paying the big bucks to those who don't deserve it.
It seems quite unrealistic to expect all these things happen any time soon. I am only hoping that more and more companies - at least gradually - realize and appreciate the true value of a good software engineer, as well as the magnitude of the potential harm a so called "average" software engineer may bring.

Conclusion

It is not the purpose of a Software Architect to produce thousands of pages of unreadable documentation. It is not to impress the business people and upper management by dropping buzzwords and talking things none of them really understands. I believe that the real duty of any Software Engineer - be it an architect who designs a large system or a programmer who develops a particular module - is to work relentlessly to minimize the complexity and ensure the intellectual manageability of every single artifact. That is what ensures fast, efficient, and successful development. Not everyone is capable of that, and those who are not usually argue that they are not given enough time - under the provided timelines - to write quality software. I am convinced that such arguments are nothing but lame excuses.

Good software engineers often half-jokingly explain their obsession with quality by saying that they are simply... lazy! A good software engineer doesn't want to do the same thing over and over again. Or even twice! A good software engineer hates having to waste hours and days on deciphering cryptic unreadable code, including their own. That is why they write code that reads like a book.

Finally, good sftware engineers hate working endless hours on chasing obscure bugs. They shrug at the thought of being part of some "sustaining team" whose only purpose is fixing bugs and putting patches on a sloppily written code. Good software engineers prefer to get things done well the first time around. That doesn't mean no bugs at all. There may always be something that was not accounted for during the initial iterations of the code. However, in a well designed quality system, any defect in a module is easily traceable and may be corrected quickly without any - or with minimum - impact on the other modules. If your organization has a "sustaining" team, that may be an indication that your "development" team is not very good after all...

No one should be proud of how complex and crafty their systems are. On the contrary, a software engineer should feel ashamed of anything that looks complex, convoluted, unreadable, and difficult to understand.

Of course, it's just my opinion. I could be wrong...