If you are a software engineer: DON'T PANIC! This blog is my place to beam thoughts on the universe of Artificial Intelligence and Software Architecture right to your screen. On my infinite mission to boldly go where (almost) no one has gone before I will provide in-depth coverage of architectural and AI topics, personal opinions, humor, philosophical discussions, interesting news and technology evaluations. (c) Prof. Dr. Michael Stal
Friday, July 13, 2007
Post Mortem
Did you ever observe software architects in projects when they are developing new products or solutions? How do they deal with failures? Interestingly, whole organizations and individual architects only seldomly learn from failure. Failures are not really impressive in any performance review or project meeting. That's the reason why they are often ignored or even forgotten. How often in your career de-ja-vouz situations appeared where you couldn't believe that the same errors were made again and again even by smart people?
Now, think about famous inventions such as the light bulb? Inventors and scientists such as Thomas Alva Edison succeed because they continuously reflect about their own activities and leverage failures in a constructive way.
So what conclusions should be drawn? If you like to find the right direction in a labyrinth use an Ariadne thread. Whenever you encounter a dead end reflect and then try a new direction. Or shortly: learn from failure!
Software architecture design is complex as you need to balance multiple and sometimes even interdependent forces. Thus, failure is a fact and not a seldom accident with respect to software development activities. There is a whole range of possible failures. In the small, some design decisions might be wrong. In the large, the whole architecture respectively project might fail. The best way to deal with such situations is analysing when and why wrong decisions were made that lead to the failure and which alternative decisions would have been the right ones. Agile processes explicitly consider the possibility of failure introducing practices such as testing and refactoring.
I made the experience that a post mortem analysis of any project (also known as architecture review) is really helpful to learn and improve my own skills and experiences as a software architect. Of course, this also holds for projects where you were not involved. Unfortunately, western culture does not really motivate writing articles about projects that failed.
The same "learning from failure" pattern does not only apply for software artifacts but also for software development activities. In the ideal case we would reflect over our results and activities daily. Correcting a wrong direction early is rather inexpensive. Correcting it lately will require substantial corrections. Shouldn't a culture of learning from failure be an important part of any (development) process?
Software architecture is the core asset of any software product and determines the quality of the result. Failure in software architecture activities has a significant impact on the implementation. Needless to say that failures must be handled with care, explicitly analysed and addressed by architects.
Wednesday, July 11, 2007
ESB
Thursday, May 31, 2007
Work Life Balance
If you want to become am accepted expert in your field, take care of an appropriate work-life-balance, or in terms of work activities: sometimes, less is more.
Saturday, May 26, 2007
Release It!
Thursday, May 17, 2007
Product Lines and Platforms
Why this platform-based approach sounds very reasonable it also reveals some challenges. Suppose, different product line instances, e.g., various mobil phones, leverage a common platform, e.g., a software platform that includes all required communication stacks such as the UI, persistence features, power management, etc. What if one of these application projects requires a modification of one of the common core assets within the platform? There are different alternatives in this context. As precondition, I assume that the modification could not be handled by variation (using a kind of variability hook in the platform), but requires the modification of a common platform component. In a naive world, we would just derive a new branch of the platform without any impact on the other family members. It is a no-brainer that this alternative is unacceptable in the real world as further change requests could emerge each of them leading to a new specialized platform version. Thus, we would end up in a sea of platforms. This is of the same dimension such as asking Microsoft to provide a customized Windows for each customer. Why is this such a big problem? Suppose, that you need to evolve the platform in the future. How can you manage evolution facing a sea of platforms? In the best case, it will work with large efforts and costs, but lead to design erosion forcing the platform team to come up with a complete new platform every second or third product line generation. Needless to say, that the RoI through re-usability which was the primary reason to establish a product line won't be too impressive in such a scenario. Thus, we need another way of handling modifications! Any change request should lead to a modification of the platform. Yes, this will require all application projects to leverage the modified platform. When the platform is a significant part of the application, then we should be ready to pay this price for the sake of platform evolution. In such an approach the platform will be evolved in a systematic way, thus reducing the risk of design erosion. However, successful platform lifecycle engineering is not only a matter of implementation. The development organization must establish a team that is in charge of platform development with a clear interface (and firewall) between the platform team and the application projects which are building the product line members. Otherwise, we will end up in exactly the same approach as illustrated in the first alternative. Of course, a platform team is responsible to meet its customers' requirements. At the same time, a platform team should be empowered to reject all change requests that have a negative impact on the majority of application projects. Moreover, application projects need to synchronize with platform development, and also send change requests and bug reports to the platform team. For these co-operation aspects, an appropriate product line engineering process and tool chain must handle all synchronization and coordination issues. MDSD techniques (Model-Drived Software Development) help to decouple platform from application developers, while increasing productivity. This is the reason why MDA (Model Driven Architecture) introduces PIMs (Platform Independent Models) and PSMs (Platform Specific Models).
To summarize, product lines and platforms denote twins, because product line engineering requires the development of a platform that combines core assets in a common way, while integrating variability management. Unfortunately, platform development is one of the underestimated activities in product line engineering, which is why many projects fail. The reason for this is obvious. Each platform modification has an impact on ALL program family members and is not constrained to a single one-off-application. In addition, the situation gets even worse and messy if you consider the fact that a platform is not only a set of implementation artifacts but also includes tests, documents, models, aspects, among many other entities. Thus, only a systematic platform lifecycle engineering approach will help successfully develop and evolve program families.
If done right, product lines can save time and money. If done wrong, they will make developer life like hell.
Monday, May 07, 2007
Doomed to fail
- Decide to use a waterfall model. If engineers complain, just tell them that you can't live with the uncertainties and missing predictability of agile approaches. Convince engineers that computer science is a science which implies that they could easily collect all input at once in the beginning, and then systematically design an appropriate software system. If they object to this statement, ask them whether this means they are not able to follow the same kind of approach as other disciplines. Make clear, that from your standpoint agile processes are only for pussies and that you don't respect proponents of this paradigm. If they tell you that agility is about addressing risks, respond to them that you consider agile developers the highest risk in any project. For the same reason, reject any test-first activities from the beginning. As a revenge, tell the development team that in order to reduce budget, multiple engineers will have to share one telephone and one workstation. This is what you understand by Pair Programming.
- Don't introduce any roles. This makes your staff clueless and helps abide to the "Jack of all trades but master of none" approach. People should feel either responsible for everything or for nothing. Using this setting, project members won't be able to ever know whom to address for clarifications. Everyone will be developer and architect. Don't assign the role of a lead architect as this would unnecessarily improve productivity. There is only one predefined role. Make the most unorganized person in your team the project manager.
- Requirements are essential drivers for any project. Thus, a perfect means is to collect a large set of requirements that are as vague as possible ("our application should be flexible"), and in the best case are contradicting ("system should be hard realtime but offer runtime flexibility") or not feasible ("we need 100% availability without any additional costs"). If architects ask you to assign priorities, just tell them that all requirements are equally critical and important. Add to this recipe another dimension by constantly adding, changing or redefining requirements over space and time, especially in later phases of the project. Needless to say, you should never remove any requirements!
- Communication should be leveraged in a proper way. On one hand, try to minimize effective communication between engineers. On the other hand, fill the schedules of your engineers with an infinite sequence of unproductive meetings. These meetings should not have any agenda, be open-ended, mandatory for all engineers, and unstructured. In addition, embrace a culture of e-mail communication with dozens of mails regularly spreading around. Tom DeMarco has labeled this kind of paradigm "Corporate Spam". Obviously, continuous demotivation is also helpful here. Never give developers any positive feedback. Only refer to the incredible and stupid mistakes they made. Introduce the "moron of the month" award for this purpose. Be a control freak and check and discuss every decision in the project, even the unimportant things. This will drive people crazy (e.g., in meetings) and will enlarge your level of joy.
- Outsourcing represents an additional means to worsen the situation. Thus, you should follow an offshoring and outsourcing strategy to play the communication game introduced in the previous discussion. Especially, selecting outsourcing teams with completely different culture is of high importance in this context. Don't let anyone figure out in the project team nor in the outsourcing locations who is in charge of what. One face to the customer won't work very well here, because your goal is to play the game of engineer torture. In the optimal case, language skills of involved persons should be low as otherwise communication would be more effective.
- Force the development team to address all technical challenges with unproven, upcoming technologies where no one in the team could obtain any experiences so far. The application should be based on MDSD, AOSD, Web 2.0. LINQ, WCF, Multicore systems, Semantic Web, REST - add your own favourites here. Tell the people in the team that they are allowed to use whatever tools, technologies, or languages they prefer. This way, the resulting heterogenous tool chain will help you decreasing productivity, while making developers think they got freedom of choice. Another, alternative approach is to force developers to uss inappropriate tools such as the C#-command line compiler, Notepad, Microsoft Word and Visio.
- At the same time don't give people any time for competence ramp-up. Motivate this by telling everyone that you consider "training on the job" the most effective way to learn new technologies. People not capable of learning on the job, are concept-addicted idiots who could be easily substituted by a bunch of chimpanzees typing on a notebook. This behavior will keep morale high. Note: I am refering to YOUR morale here.
- Architecture should be addressed inappropriately. In most cases it is sufficient to prescribe the exclusive usage of UML. As another option consider to develop dozens of different DSLs for even the smallest subdomains. Enjoy when engineers then try to come up with integrated solutions. Architecture documents should have no specific structure, never include all relevant parts, only motivate the "what" and never the rationale. Don't allow any domain modeling during start-up to make sure that stakeholders DO have a DIFFERENT understanding of the underlying domain (concepts). Never introduce architecture guidelines for crosscutting concerns such as fault management, security, or logging. Don't assign the responsibility for subsystems or such cross-cutting concerns to single individuals. This strategy supports mixing domain-specific functionality with infrastructural and non-functional issues which is a constant source of all kinds of errors (-; Applying patterns should be considered a tabu as all other kinds of best practices. To be even more effective, introduce anti-patterns and pretend they are patterns. Last but not least, don't prescribe any locations where to put all those documents. Recall your mantra of IT sadism "Searching is better than finding".
- Introduce re-use by copy-and-paste only. Argue that you are expecting developers to think in terms of open source and that from your point of view this implies a copy-and-paste philosophy. In this context, it would be a mistake to enforce any configuration management tooling. After a while, the application code will be flooded by re-used code fragements that - in case of luck - were changed in the meantime. As soon as an error pops up in only one of these fragments, all developers will feel the pain. Re-using binary components should be considered a bad idea as it will constrain your control. Moreover, enforce and motivate your team to develop everything from scratch. Don't trust any 3rd party middleware, tools, operating systems, but let developers develop these things. Using this approach, the project will focus more on infrastructure than on domain which supports your sabotage stategy.
- Make your system a SOA system. Come up with a great number of potential integration problems and distribute the command to solve all these issues with loose coupling, i.e. using a service-oriented architecture. Invite consultants from market analysts and other companies to tell your team about emerging business and technology directions. Your team will then fall into the SOA trap, forced to consider topics such as Governance, BPM, SOX, EAI, BAM, ESB, REST, WSDL, BI, SAP, etc. This recommendation is a guaranteed success factor as people tend to stop thinking whenever they discuss the SOA panacea. Maybe, they'll recognize that none of this technologies has any relevance for the project, but then it will be too late, anyway. And if the project fails, there are lots of project members you could easily assign the scape goat role. Here, the "I am the Wizard of Oz surrounded by evil demons and incompetent engineers" argument will convince senior management. Take a financial reward from management for opening their eyes and then leave the company. Now, it will be a clear SEP (somebody else's problem).
Note, that knowing these recommendations does not imply that I have personally seen or applied any of them. Nor do I claim that the list above is complete. You may tell me about your personal experiences by adding a comment to this blog. I am open to any feedback :-)
Thursday, April 26, 2007
Danger Seekers - A day in the life of an anonymous software architect
- 08.00h: Scanning the e-mail for any recent project catastrophes.
- 08.15h: Asking the product manager and the requirements engineer what the customer means with "system should never fail but there is no extra budget for replication hardware and software".
- 09.00h: Meeting with subsystem developers, because they didn't follow the architecture design nor any guidelines. Told me that they understood every detail from the strategic architecture document I have conceived and written. However, they assume, I were not able to understand my own architecture which is why they tried to illustrate what they think I should have meant.
- 10.25h: Telephone conference with outsourcing site. They didn't know what interfaces to provide and what interfaces to use. Discussed the same issue over and over again with different people in the past but obviously they didn't share the information in the team.
- 12.00h: Having lunch with the project manager and a software process consultant for iteration planning. We are behind schedule and aleady exceeded the budget. Our table was surrounded by staff from other projects where they kept complaining about architects, senior management and the rest of the universe. What a motivating lunch.
- 13.15h: Talking to the testers. They are a little bit unhappy because some managers told them that they don't need to test as this would endanger the release date. According to this manager, the project got high quality developers who provide high-quality code. Thus, testing is overkill. Could raise their motivation after showing them the newest Dilbert cartoons.
- 13.35h: Talking to an external consultant. He is not familar with the domain, nor with the technologies. To be honest, I am not sure whether he got any expertise. Only recently he got his university degree. But senior management is relying on his statements as he is employed at one of those famous consulting companies. "They never make any mistakes", one of my senior managers mentioned.
- 14.30h: A middleware vendor is explaining why her product could solve all the project's problems. It can even solve problems we don't have. What a cool technology!
- 15.56h: One of the developers is calling me. He asks me to explain again why patterns are so important. He fears, applying patterns is reducing his creativity. Moreover, his productivity will also suffer as he must read all those pattern descriptions.
- 16.20h: The phone is ringing. It is our senior manager. He urgently needs some slides on the ROI of software architecture with some concrete numbers. He obviously knew that this presentation would be required for weeks, but one day before the meeting he is asking me for help. I must keep calm.
- 16.45h: Speaking with some developers. I just found out they have never built any Java EE application before. Our management thought, they are so smart that they could learn Java EE in a few days. That is what they call training on the job. I've decided to schedule some seminar days where I will provide them a Java EE hands-on-training.
- 17.20h: Some of the tests have failed. The product managers sent me some vague new customer requirements - all of them with highest priority. Software engineering drives me crazy.
- 18.20h: Reading the news. The new operating system will be postponed to next year. It is the goal to add some new features in the meantime and to further improve the quality. Going where no man has gone before, as it seems. A friend of mine working on the OS had told me some other background stories. Something about severe quality problems and the like. For heaven's sake, we are not alone!
- 19.45h: Drinking beer with some project members. Interesting to hear some war stories from earlier projects that failed. After several drinks my colleagues become very informative and open-minded about what they really think about the project. Should I quit tomorrow and search for a better job? No, that wouldn't make any difference.
- 23.30h: Reading some new technology stuff and one of the cool architecture web sites. This way I am learning a lot about new technologies. But, of course, the best technologies in the hands of some insane software developers or, even worse, managers can cause a lot of harm.
- 00.30h: Very exhausted now. It is time to go to bed.
- 06.00h: The alarm clock is ringing. What a nightmare. I had a dream of a meeting with managers who presented their favourite software architecture to me. Then I got the task to improve their architecture. Whenever I was done, a new meeting was arranged where they confronted me with yet another architecture and the whole pain started again. I could feel Sysyphos' pain.
- 08.00h: A new working day full of joy, honour and interesting conversations is waiting.
Monday, April 23, 2007
On Apples and Oranges
- While it is certainly true that industrial hardware production happens with a huge amount of re-use of standardized components, software engineering is more like hardware engineering. At design time hardware developers face exactly the same kind of problems when identifying requirements and mapping these to concrete circuit plans. This is the same mixture of craft, art and science software engineers rely on.
- Software systems experience a lot of more changes during design and implementation than their non-software cousins. Would you ever expect that shortly before a Boeing 767 is ready to be delivered someone will ask the engineers to change the body or engines. But that is exactly what happens in software engineering. Changing an airplane engine during flight? Impossible for aircraft engineers but expected from software systems. Add to this that customer requirements are much more unspecific, dynamic and unreliable in software development projects.
- Software must run in a lot of different contexts. Take operating systems such as Mac OS X, Linux, Vista as an example. They must support different kinds of hardware systems, different kinds of drivers, different kinds of user preferences. Such flexibility is not provided by any other product. For the same reason, large-scale software re-use such as the component or service market places we often dream of will remain dreams despite of standardization.
- Some people tend to make humorous comments about software engineering and rocket science. The problem is that in terms of complexity many software developent projects reveal a large degree of complexity. Software engineering sometimes IS like rocket science. Costomers and developers often ignore this complexity which is caused by operational and developmental qualities. The largest software projects are way larger than any comparable non-software projects. Keep in mind that the same holds for all the software tools and infrastructures.
- An incredible amount of bugs lurk in software systems due to a lack of quality in software developemt. True or false? Have you ever seen the error list of hardware systems such as that of new CPUs? Believe it or not, but I claim that the error rate in software systems is not higher - at least not significantly - than the one in comparable non-software systems. In addition, we should keep in mind testing is much more difficult in software due to the higher diversity of the software ecosystem.
I propose the following experiment: take a bunch of hardware developers who have become software engineers and see whether they are more productive and offer higher quality when developing software than their colleages without that background. From my viewpoint the immaturity of software engineering is a myth. Wrong statements repeated often will not become correct statements. I am wondering what other people think about this.
Saturday, April 21, 2007
Contracts - continued
- In distributed application environments such as SOA (to add one of the inevitable buzzwords here) the interfaces provided by services and components have to be described by contracts, because generator tools require these contract descriptions to generate proxies and stubs.
- In a container (no matter whether it is a lightweight container or a heavier one) contracts are required so that a container can handle component deployment in a proper way.
- Developers may follow the guidelines of contract-based programming as introduced by Bertrand Meyer in Eiffel by leveraging preconditions, postconditions or invariants for their abstract data types.
- All kinds of unit tests or integration tests require at least implicit knowledge of contracts as contract violations are one potential source of quality problems.
- Contracts are the means to implement and validate Service Level Agreements.
- In future scenarios of service compositions quality annotations available in contracts might help to determine if a particular workflow can meet quality requirements such as response times.
- Security feautures and measures re based on contracts that specify who is allowed to do what in a system.
But how can we describe contracts? This is an important question that has to be addressed in this context. Again, we should differentiate between implicit and explicit contracts.
An implicit contract basically says: here is the implementation and whatever it provides to its consumers or expects from its consumers is part of the contract. Implicit does not necessarily imply that there is no documentation of the contract. There might be comments in the source code that describe pre- and postconditions checked or guaranteed by the implementation. Alternatively, the contract might be documented in an API document.
An explicit contract is a contract that is expressed in a kind of contract DSL. A WSDL description is a typical example of an explicit contract description. Of course, the contract DSL can also be expressed with the means of the underlying programming language such as using annotations in Java or attributes in .NET.Another question in this context is whether contract violations are checked and whey they are checked. If a generator takes a contract DSL and generates implementation artifacts that are compliant with the contract, this helps with automating otherwise tedious and error-prone tasks. However, this is far from being sufficient. What about people taking and extending or changing generated code? Compile time checking of contracts by a compiler reveals the same issues. Runtime checking of contracts is the most secure approach while also causing potential performance penalties. Take security breaches such as buffer overflows as an example for the consuequences of not checking contracts. Or consider Java class loading which is checked at runtime. Another example for runtime contract checking is the .NET CLR security architecture where the security contracts may either be expressed programmatically or descriptively.
In architecture modelling languages such as UML you may apply language features like OCL to describe contracts. Obviously, in software architecture design all contracts must be described explicitely without prescribing how these contracts are eventually mapped to implementation artifacts. How do we determine the contracts of architectural entities? External entities we are integrating should already provide contracts. For all other constituents we should take functional and non-functional requirements as the base to defer contracts. For discussing design using CRC cards we could add contract information either on extra cards annotated to CRC cards or introduce a contract part at the bottom of the CRC card. With other words, contract-based programming should be an important discipline for software engineering in general.
Friday, April 20, 2007
What is (in) a Contract?
There is another dimension of contracts. Contracts might be explicit or implicit. In the former case we need description languages to express the contract, while in the latter case contracts are either part of the infrastructure (such as a programming language) or implicitly hard-coded into application functionality.
In contracts we also may find different restrictions for different stakeholders. Think of a C++ class with private, protected and public sections. Depending on whether you subclass or use a class, you might experience more or less access restrictions.
In distributed systems contracts are ubiquitious. Think of EJB with its deployment contracts, component/container contracts, or client/component contracts and think of SOA platforms!
As we recognize, contracts are much more complex and important than we tend to believe.
Why - in the hell - are contracts relevant for softwae architecture? They are essential as they define various essential aspects when dealing with the boundaries of a particular entity such as a service or component. Contracts define the interactions that may take place between the service or components and their clients as well as the qualities and constraints associated with these interactions. Only if there are formal contract descriptions, tools can help with the (automatic) composition of artifacts (e.g., MDSD) as well as with the validation of architectures and implementations.
If you are going to model a new system, keep in mind to describe the contracts of the interfaces introduced. Otherwise, developers or architects won't be able to understand how to access this interface in a proper way. A surprisingly large amount of problems in software engineering is caused by missing or incomplete or inconsistent contracts. Hardware developers won't ever build any system without knowing the contracts of the system constituents a.k.a. specifications. Why should we as software engineers? Don't believe in statements like "contracts are for pussies". Only contract-based development leads to high-quality architectures and implementations.
Friday, April 13, 2007
The Complexity Of Interface Extraction
- Granularity: Suppose, you have developed a Java class that implements a graph-like structure such a s a tree. You got classes such as Tree and Node. As you are a smart programmer, you already have extracted interfaces such ITree, INode instead of bloating the class interfaces with oceans of methods. When you navigate to a subtree, a method such as navigate(theChildINeed)returns the right child tree. If you now naively convert all of your classes, i.e. the aforementioned interfaces, into remote interfaces, things will rapidly get messy. Each invocation of navigate will now return another remote object. All of a sudden, network traffic will explode. You can't simply develop remote classes as POJOs/POJIs or PONOs/PONIs. For the same reason, it does not make sense to transfer fine-grained data in service invocations. Sequences such as node.setName("Michael"), node.setStreet("Marketstreet"); node.setCity("SFO"); are DONOTs in any SOA or Remoting context. Firstly, they imply a lot of traffic. Secondly, the calls are related and might force the server to keep session state across method calls.
- Impedance Mismatch: In heterogeneous networked environments (such as SOA) you'll always face the problem of mapping between the object model of your language and the object model of the remotung technology, and vice versa. Whenever you try to transmit a HashTable between a .NET application and a Java EE application over a SOAP connection, things will turn out to be like hell. This does not work without significant work arounds. As a relational database programmer or O/R victim you will now what I am talking about. Most remoting solutions will only provide the least common denominator approach in their object model. As soon as you are trying to integrate the richness of your preferred programming language into that middleware, you are calling for trouble.
- Operational Qualities: Add to all this the complexity of operational qualities such as security or performance. Basically, you are now forced to combine the security infrastructure of your application with the security infrastructure of the middleware. And you have to take performance of your application objects into account, but also the costs of communication. And you have to integrate distributied transaction monitors with local transaction APIs. And ...
- Contracts: An interface is not just a matter of syntax. Nor is it just a set of signatures. It is also about semantics such as preconditions and postconditions. A finite state automaton could specify in which order which methods are allowed to be called. The contract might also deal with local policies and define the protocols with which clients can bind to the service. Note, that this is much more than what you would expect from an interface in a non-networked environment.
- Architectural Balance: Let us suppose, we have magically solved all these issues. How should we compose our application to remote components or services. We might end up in an ocean of components. Or we might end up in an ocean of service interfaces. Architectural patterns such as Layers or Extension Interface are helpful in this context. I must confess that no rule of thumb exists for this architectual structuring. It really depends on the concrete problem context how to address this issue. One really helpful way is to think in terms of roles and responsibilities.
Basically, what all this means is that when you start to develop your distributed system from scratch, you need to thoroughly consider the issues explained above. However, when you are in the situation to integrate legacy code, things are much more complex: you'll need a whole bunch of refactoring activities to reorganize your application in such a way that is will consist of classes and interfaces that easily fit into the middleware ecosystem. In the best case, it might be sufficient to add some wrapper {facade} objects. However, in the case of bad luck you will even need re-engineering efforts.
I tried to shed some light on the complexities of application integration and provisioning of remote interfaces. What appeared so obvious and simple in the beginning, in fact is one of the most complex tasks engineers have to cope with. Efficiently building efficient distributed systems implies thorough treatment of all these issues. Believe me, prevention is better than cure!
Wednesday, April 11, 2007
Oh, Happy Day !
- what if the user provides the wrong credentials?
- what if you encounter memory outeage when instantiating new objects?
- what if the communication breaks down?
- what if you can't read the user's preferences from the file system?
- what if data already has been locked by someone else?
- what if the HTTP request does provide a user agent you don't support?
- ...
You are a smart architect who never forgets any detail, a perfectionist in terms of software engineering. Thus, you are starting to cover all these exceptions in your architectural design. A few use cases later you are lost in design erosion, trapped in a web of entities and relationships.
On one hand, it is essential to consider all of these exceptional cases and list them in the use cases. Most of the exceptional issues are cross-cutting, re-apperaring in different places of your system. On the other hand, if you start to consider them from day 1, you will be doomed to fail in any non-trivial development project.
The recommendation in architectural design thus reads as follows:
- In the beginning only start with the main flows (the happy day scenarios). Those should be the ones driving and guiding your core architectural design.
- In addition, you should come up with project-wide strategies how to cope with exceptional cases. Strategic documentation should be made available and mandatory for all developers with description how to deal with exceptions, transactions, synchronization issues, and the like.
- The exceptional cases will together with non-functional requirements help extending or changing the domain-specific architecture and infrastructural parts.
Of course, it would be fatal to rely on happy day scenarios only. How often, did I encounter finite state machines with all "happy day nodes and edges" where no one thought about the error states, because errors are for pussies. It is not the matter of ignoring exceptional cases. It is more on doing happy day scenarios first to obtain a stable strategic base-line and then thoroughly integrating the exceptional cases.
From now on, you should always think of "happy days" when modeling your architecture :-)
Monday, April 09, 2007
Brothers in arms - SOA and Product Line Engineering
Now, compare all this to Software Product Line Engineering (SPLE, see my special posting on that issue):
- Services and other common parts (such as legacy applications or platforms) are core assets
- Applications (composite applications and business processes) denote the members of a product line
- Service engineering resembles domain engineering and application engineering is identical.
Sooner or later you'll reconginze that SOA engineering is best organized as a SPLE process.
Scoping might be a little more difficult but you should read my last posting how we could cope with this challenge. You might also consider to provide a generative architecture as described in another recent posting as well as in what I wrote on ULS.
From my viewpoint the only conclusion to draw is that SPLE and SOA engineering should be considered as two sides of the same coin.
The Scope of Product Line Engineering
This brings me to another important point. I was also asked whether product lines are always a simple partitioning in domain engineering and application engineering. No, they are not. Take car manufacturing as an example which many consider as the prototype of Product Line Enginering. If a car type is manufactured as part of a product family, it will contain common parts all members of the family share as well as variabilities such as different colors, chassis, and so forth. Thus, we have domain engineering where we produce the common core assets and application engineering where we build and customize the individual cars. So far, so good. If we think this a little bit further, some of the assets we use will themselves be parts of other product lines. Maybe, the supplier of the entertainment and navigation system will also leverage product line engineering. Or maybe, the car manufacturer will provide other assembly lines where core parts such as the doors of the car are produced in a product line approach. In other words, a product line can be a combination of different product lines intertwined with each other in a pipeline.
But it is also possible to find the other way round? Suppose, we are going to build a generic web shop application for different customers. We are setting up two separate product lines, one for SOHO and another one for enterprise customers. After a while we find that both product lines can be based upon an additional product line where the common parts for the SOHO as well as for the enterprise web shop are developed. In the SOHO and enterprise product lines we are then just adding the deltas. Obviously, such a setting only makes sense if the SOHO and Enterprise Web shop solutions reveal a lot of differences. Otherwise we would better establish one single product line and handle the differences of both shop variants by variabilities. By the way, finding the appropriate approach is also an important outcome of scoping.
In one of my next postings I will address the issue of why SOA engineering can be considered Product Line Engineering. So, stay tuned!
Sunday, April 08, 2007
Generative Software Architecture
- we could define the basic infrastructure and let complex parts be integrated into this infrastructure
- we could define a set of rules to dynamically build and evolve such an infrastructure as well decribe integration constraints for parts
interacting with and over the infrastructure or - we could combine the aforementioned approaches by defining a small set of rules and smaller predefined infrastructural parts
Those rules are not only axiomatic rules as in a language grammar, but also define constraints and requirements for the parts that are integrated into the infrastructure. In such an approach, the non-functional properties must be derived from those rules and constraints.
They cannot be evaluated in advance for the reasons already introduced. Obviuosly, to cope with future challenges, such a system must also provide extension rules and hooks to even adapt the infrastructure to changing requirements.If you think about it: Why could we come up with a technology such as the Internet with all of its complexity? Internet technologies already
fulfil the properties mentioned above. The Internet consists of smaller infrastructures, e.g., Internet backbones. Technologies such as IP, DHCP, or DNS define a rather simple (but not simplistic!) rule set for extending and evolving the infrastructure as well as for integrating servers and clients into the infrastructure. All those very simple technologies are combined to a highly evolvable and dynamic, large-scale system which is open for extension. Basically, HTTP and the Web are only one of those extensions. As a real-life example take the brain which consists of rather simple constituents and combinations between those constituents, but reveals the most complex emergent behavior we have ever dealt with. From my viewpoint, we should direct our research activities to those generative architectures, especially when dealing with large-scale software systems. Emergent behavior is the key to master complexity in such systems.
Saturday, April 07, 2007
Non-functional requirements
Another important point in this context is desribed as requirements elicitation and requirements engineering. It is interesting how unusable many requirement specifications are. Statements such as "System should provide flexibility" are worthless. What exactly do these stakeholders mean by flexibility? Which parts of the systems should be flexible and when? Of course, you are also doomed to fail if you get a bunch of requirements with no priorization. The same application would look completely different in case security is the number one requirement compared to the same application when performance had the highest priority. So, my second rule simply says "Architects and key developers are in charge to clarify all requirements including their priorities. For each pair of requirements, it should be clear which one has precedence in concrete design decisions". As many requirements might contradict each other, this is inevitable to survive. If your management does not agree to detail requirements or prioritize them, either refuse to be in the project or, more realistic, document this fact and possible implications so that all stakeholders know they might encounter project failure. Unfortunately, non-functional requirements are incredibly diverse. Performance is totally different to security. Even security might mean a lot of various things such as authentication, authorization, confidentiality, name it. Thus, my rule 3 is: "Detail all non-functional requirements in the beginning of the project, maybe establish checklists, and use these information to clarify requirements".
Developmental requirements such as flexibility (e.g., changeability) have to be addressed early only if you are going to design a product line where a Commonality/Variability analysis is of foremost importance. If this is not your primary setting, developmental issues should be treated as a tactical refinement of your software architecture. Thus, the strategic base line architecture is primary influenced by the domain logic and by operational requirements. The integration of developmental issues is a further refinement activity. Considering issues such as extensibility in early phases typically leads to generic and inperformant architectures with instantiations of patterns such as Observer, Interceptor or Strategy. In addition, your job as an architect tends to continue forever in such environments. This is where I'd like to add my fourth rule "Domain logic dominates infrastructure; infrastructure dominates operational requirements; operational requirements dominate developmental requirements". Of course, it is also important to specify when operational requirements appear in the running system (run-time, install time, compile time, ...) and who is in charge to inject them (operator, user, developer, ...).
As of the huge diversity of requirements it is not easy to come up with general rules of thumb for all of them. Some observations might be interesting in tbis context: A given requirement might be invasive (i.e., cross cutting through other parts of your system) or non-invasive (i.e., it is sufficient to add some additional infrastructural components to the architecture). The same requirement might be invasive for one system (security in all its aspects must be supported everywhere) or non-invasive (we just can add a firewall and don't have to care about security at all). A requirement might me measurable such as the response time to a method request or not (such as removability). Requirements that can be measured can be covered by testing, all other requirements must be covered by architecture and code analysis. Last, but not least, my rule 5: "Classify your requirements and provide strategies how to deal with them in architecture design activities".
Of course the overall goal of software engineering must be to
- Let engineers focus on their domain by strong separation of domain aspects from other aspects.
- Automate where possible and useful.
There are several efforts to support these goals such as Model-Driven-Software Development, Product Line Engineering, Aspect-Oriented Software Development, SOA.
One way could be to provide DSLs for the domain, additional domains for the most important non-functional requirements, a technology that easily lets project teams integrate these DSLs under one umbrella (e.g., by leveraging AOSD techniques, Semantic Integration). A Product Line Engineering approach could define core assets which become then part of platforms or frameworks. With SOA, platform dependencies could be overcome. Is this just Science Fiction or a reasonable future vision? Let's work on this :-;
Thursday, March 29, 2007
Back in Town
If you like to read the Ph.D., then obtain a PDF version from the university library. Since the defense ceremony was very impressing (in an old-fashioned and nice setting), you can also look at my flickr photo set. By the way, Groningen is a lovely town in the Netherlands. You should definitely visit it whenever you are in the area. In addition, all Dutch people I met are extremely nice. I am glad that everything is done although I am already missing the great co-operation with Jan. Now I am ready again for future blog postings.
Sunday, February 18, 2007
Simple is Beautiful
The fundamental properties of of these examples are as follows:
- The entities and their interactions are simple
- If any of these entities and interactions are removed, full coverage of the problem space is not possible anymore
- The same design principles are applied orthogonally throughout the system. Such design principles cover functional as well as non-functional properties
- The system design provides atomic components on top of which higher abstractions (e.g., subsystems) are built, thus resulting in a hierarchical instead of a flat structure
If you think about the properties above, you'll see that a violation of one of them immediately results in a non-simple (i.e., complex or simplistic) system. For instance, you might have simple entities and relations, but when the system consists of a giantic flat structure, you might be lost. Trying to understand Windows Vista by looking at its assembly code, simply does not work. Simple systems offer what I call architectural beauty. Just by using a small set of easy-to-understand concepts, you can even cover complex problems in a domain.
What architects tend to forget is the fact that humans are not able to consider and understand more than 7 entities at the same time.
Thus, upcoming Ultra Large Scale systems will only succeed if they rely on a simple fundament consisting only of a few core concepts that can be combined to a broad spectrum of emergent behavior.
Simple does not mean simplistic. A system is simplistic when its core elements are not capable of covering the whole problem space.
How can we achieve simplicity? Simple software architectures often reveal a high pattern coverage, use abstraction concepts such as components or services, and use the same solutions for the same problem contexts. Evolving a simple system might happen in two ways, by evolving the core concepts or by applying the available abstraction mechanisms.
By the way, in case you wonder how to check whether an architecture is simple: one test for simplicity could be the following. An architecture is simple when is can be explained to other experts in a few minutes without using visualizations.
It is important to mention that in a software project it is not sufficient to deal with simplity in the software architecture. Simplicity should also be the leading principle for other aspects such as the process, the tools, the technologies, the programming languages, the models, the DSLs, and so forth. Also keep in mind that it is generally insufficient if each of these aspects is simple. You might use a simple but powerful language such as Lisp and will be completely doomed to fail when trying to build a multi-tier enterprise system using Lisp even if the architecture is simple. This implies, that the combination of simple aspects must be simple itself. Therefore, consider simplicity a holistic approach.
Mind the "Keep it simple, stupid" principle to make your professional life simpler but not simplistic :-)
Sunday, January 28, 2007
The Singleton Patterns and The Concept of Identity
Basically, what the Singleton does is to guarantee the existence of one single instance of a class.
Example from JavaEE:
// get ConnectionFactory via JNDI
ConnectionFactory cf = ... // single instance
Connection c = cf.CreateConnection(...);
The factory uses the Singleton pattern such as in:
class ConnectionFactory {
static private ConnectionFactory _instance = null;
protected ConnectionFactory() { ... };
static public ConnectionFactory instance() {
if (null == _instance)
_instance = new ConnectionFactory();
return _instance;
}
}
For sake of brevity I didn't care about thread safety. For our purpose you can just ignore this fact. In the code you will recognize that no external user of the class can access the protected constructor so that the only way to obtain an instance is calling the static factory method instance().
This way, there will be exactly one instance whenever one or more clients access the class.
There are many problems with this approach. The most important one is that a Singleton defines a global variable such as ConnectionFactory.instance().
Suppose, we need exactly one object instance. Do we really need to rely on Singleton?
Let me introduce a simple example.
class ThePrinter {
private String printerAddress;
public void print(String fileName) { ... }
public ThePrinter() { printerAddress = MY_CONSTANT_VALUE; }
}
In the class above, we can provide multiple instances and each of these instances will semantically define the same object (thus we should provide appropriate equals and hashCode contracts).
From an identity viewpoint, we need to constrain our class to provide objects with the same identity. The way a Singleton tries to guarantee this is by only providing one single instance. But it is legal and much more appropriate (in terms of OO pureness) to achieve the same result using an immutable class as shown in the printer example above.
Another option of course would be if we need to constrain the numbers or kinds of instances created by a class such as in a Manager or Pool class. These only allow creating specific instances or a maximum number of instances. Thread and connection pools are typical examples for such behavior. In this context, a Singleton is a Pool with capacity == 1. I won't show you more code here. You can google for Peter Sommerlad's Manager pattern.
Another "inverse" kind of approach in terms of identity is the following. Suppose, you got thousands of users on your Web site. You started with providing one volatile user object for each online user which made your system less fault-tolerant and more resource consuming. How could you change this? In other words, how could you provide a limited number of instances of user objects (# user objects << # online users)?
a) implement a pool of fixed user objects that wait for incoming requests (Active Object pattern)
b) when a request comes in, it is enqueued
c) one of the idle objects dequeues the request
d) it retrieves the user id from the request and looks up the user's data in the database
e) it operates on the user data and stores it back to the database
f) the result is returned to the client
g) the user object is ready to take another request and thus another identity
This approach does even work if multiple user objects run under the same user identity. In this context, each pool object can temporarily take any identity because the identity's data itself is persisted somewhere else. Thus, identity is not the same as instance.
Keep this in mind when you next time feel addicted to solve an identity problem with the Singleton pattern or other weak approaches.
It is surprising how often architects and engineers forget to think about identity and how to design and implement it.
Thursday, January 25, 2007
Architecture Refactoring
As you might remember, Joshua Kerievsky came up with "Refactoring To Patterns" in 2004. The basic idea behind his excellent book is to identify places in an implementation that use proprietary solutions for problems which should be better solved using patterns. I also want to give you an example for this approach. Suppose, you have implemented a class ShareValues that contains a list of consumers. Whenever a share value changes, you iterate through the list and call event handler methods on each of these objects. While simple to implement, the flip side of this approach is the tight coupling unnecessarily introduced. As soon as new consumers are added, we have to manually modify the ShareValues's list of consumers. Obviously, applying the Observer pattern comes to our rescue here. The advantage of refactoring to patterns is that we are able to introduce a higher abstraction level.
If you think that even further, we enter the world of what I call Architecture Refactoring. In Architecture Refactoring we refactor the architecture itself.
Possible Examples:
- Partition Responsibilities: If a component or subsystem got too many responsibilities, partition the component or subsystem into multiple parts, each of which with semantically related functionality.
- Extract Service: If a subsystem does not provide any interfaces to its environment but is subject of external integration, extract service interface.
- Introduce decoupling layer: If components directly depend on system details, introduce decoupling layer(s).
- Rename Entity: If entities got unintuitive names, introduce appropriate naming scheme.
- Break Cycle: When encountering a cycle on subsystem level, break it.
- Merge functionality: If there is broad coheshion between two modules, merge them.
- Orthogonalize: If two parts of an architecture introduce different solutions for the same problem, choose one preferred solution and eliminate the other.
- Introduce strict layering: If in a layered system, a layer accesses lower layers without necessity (relaxed layering), enforce strict layering.
- Introduce hierarchies: If several entities are only variants of a particular entity, introduce a hierarchy.
- Introduce Interceptor hooks: If we have to open an architecture for out-of-band functionality according to the Open/Close principle interceptors should be introduced.
- Eliminate dependencies by dependency injection: Reduce direct and wide-spread dependencies of Parts in a Whole/Part setting by introducing a central runtime component (Whole´) that centralizes dependency handling with dependency injection.
So far, these are only suggestions of potential candidates. But you can see the possibilites now. Obviously, we should collect a whole catalog of such architecture refactorings. Note, that higher-level refactorings might incur lower-level refactorings the same way architecture patterns often refer to design patterns. Extract Service will very likely lead to an Extract Interface refactoring if the implementation already exists. Orthogonalize is heavily leveraging refactoring to patterns.
From my viewpoint, we have just started to understand and apply refactorings in a more holistic context. As the old saying goes, we have just seen the tip of the iceberg. Architecture Refactorings will guide architects to identify potential problems in a software architecture and also provide them with refactorings to solve those issues.