Writing functional requirements for a huge software application is not an easy task. This article describes different levels of s/w requirements: general scenarios, use cases, algorithms and checks, object types’ descriptions, and how they are connected to each other. It shows a way to describe requirements so that developers can effectively use them for their work on all system layers: database, application server, GUI.
Once a friend of mine said that instead of reading requirements, he usually invited an analyst to a cup of tea, they sat down together and the analyst told him what should be implemented. My friend is an intelligent person and a good programmer, and the reason why he gets knowledge of the requirements in this way is not because he is too lazy to read the documentation, but because even after going through it, he is usually not sure what should be implemented. In this article, I want to describe a way to write requirements for a software product so that developers not only use them, but also participate in their writing. Based on my experience, I would like to show the way to write software requirements to almost completely describe what should be implemented.
About Our Project
The aim of our project was to create a part of an internal ERP system for one of the largest Russian distribution companies from scratch. The system was designed to replace the old one written in the late 90s. As a result, the platform and one of the business modules were implemented. The product consisted of about 120 business object types, 180 database tables, 30 printing forms.
Note the approach shown below is mostly suitable for enterprise systems which are being built using an object-oriented approach: CRM, ERP, accounting, document management systems, etc.
Our project documentation consisted of the following parts:
- The common part
- Business requirements
- General scenarios
- Use cases
- Algorithms and checks
- System requirements
- Non-functional requirements
- Integration requirements
- User interface requirements
- End user documentation
The common part described terms and business roles used in the project. The rest of the documentation including, for example, test cases are based on the definitions given here.
Business requirements are what business users needed, they consisted of several levels: general scenarios, use cases, and data processing algorithms. You can find how to develop them in the book Software Requirements by Karl Wigers.
System requirements described methods and properties of all object types in our system.
In this article, I will discuss these three parts in detail. Also to those who want to know more about Non-functional requirements, I can recommend the excellent book, Architecting Enterprise Solutions: Patterns for High-Capability Internet-based Systems by Paul Dyson and Andrew Longshaw.
Let's take a closer look at the glossary and discuss why it is so important.
A glossary is a list of terms used in a project and their definitions. Very often, discussions between project members about the particular functionality come to a standstill. The situation is even worse when after a long talk, people go to their workplaces with different understanding of what they have just talked about and what needs to be done. In many cases, this happens because the team members didn't agree on the precise meaning of some terms. Sometimes, even the simplest words cause problems: what a user or a client is, what the difference between a role and a group is, etc. To eliminate such problems, we tried to give each term a definition as precise as possible.
Let’s discuss the term User as an example. Wikipedia gives the following definition:
A user is a person who utilizes a computer or network service.
It didn’t suit us because our system stores data not only about users who can login into it, but also about inactive and even deleted users, i.e., about users who used the system before but cannot do it at the moment. Moreover, we have data about potential users, we can create an account for a client company’s employee who may (or may not) later get access to the system. Our definition:
A user is a person who had, has or possibly will have access to the system to perform operations.
Now a programmer, having read this definition, would immediately understand why the Login property in the User object type is optional.
Many terms are related to each other. The term Operation is used above, so here is its definition:
An operation is a set of actions that make up the content of one business activity. The operation must comply with the requirements of ACID (Atomicity, Consistency, Isolation, Durability). The set of operations of one module represents the client-server interface of this module.
As you may see, this definition is very important, it not only connects a user and his business activities with functionality that should be implemented, but imposes requirements on how this functionality should be implemented (this was determined earlier when the system architecture was developed) - business actions must be inside a transaction.
We kept working on the list of terms as we went along. We maintained its completeness, i.e., we tried to ensure that the documentation had no undefined terms. There were cases when we had to change definitions. For example, after several months of use, we decided to replace Counterparty with Company. The reason was very simple - it turned out that no one used the word Counterparty in conversations. And so, it had to be replaced with something more obvious.
Often, we had to interrupt our discussion and reread terms in order to understand if the discussed functionality fit our definitions. Sometimes, to maintain consistency of our requirements, we ultimately had to either change the implementation or rewrite definitions.
As a result, we had about 200 business and system term definitions in our list; and they were used not only in all the documentation, including, for example, the technical design developed by programmers, but also in conversations when we verbally discussed some functionality.
The glossary was the first part on which all our documentation relied. The second part was a description of business roles.
Since our system is an interactive one, there are only two subjects: users and the system. But in an enterprise application, users have different roles and permissions. Possibly the simplest example is the difference between a system administrator and other users. In a large system, there can be dozens of roles, a business analyst should think about them in advance, define them and specify them in general scenarios and use cases as actors, i.e., users that interact with the system in this or that scenario. The list of business roles was used to create user groups in the system and assign permissions to them. Test engineers tested the system using different roles.
Since the company already had well-established departments and functional responsibilities, we didn’t have to make up roles from scratch, we just based them on the analysis of the existing employee responsibilities. We assigned permissions to roles after development was finished and before deployment because only at this time the list of functional permissions became stable.
There are two examples below:
|Business role ||Abbreviation ||Department ||Responsibilities |
DM sells goods to clients, creates client buying requests, bills and shipments in the system. Every DM has a particular list of clients he/she works with.
Clients department specialist
CDS discusses shipment details with DMs and clients (shipment and delivery time, missing parts, etc.), prepares papers for shipments and logistic documents for delivery.
Now when we have all our terms defined and have clearly described who interacts with the system, we can discuss how to write requirements.
Levels of Requirements
One of the important concepts that we applied developing the requirements was dividing them into levels. Alistair Cockburn in his book Writing Effective Use Cases distinguishes five levels, but we used four. Here they are:
- General scenarios - Cockburn’s white level
- Use cases - Cockburn’s blue level
- Algorithms and checks - Cockburn’s black level
- System requirements - there is no such level in the book, in fact, it is under the black one
The structure of our requirements was a tree. General scenarios were refined by use cases, which, in turn, had links to checks and algorithms. Since we used a wiki software, we could easily implement such a structure. Use cases, algorithms and checks used the object types, their properties, and methods described in the system requirements.
On the one hand, this concept allowed us to describe the current piece of requirements in as much detail as necessary at a given level, relegating other details to the lower one. On the other hand, at each level, it was possible to go to the higher level to understand the context. This was also provided by the wiki functionality: often scenarios, use cases, algorithms and checks were written on separate pages, and the wiki allowed to see what pages link to the current one. If an algorithm was required in more than one use case, the algorithm was placed on a separate page. Usually our developers implemented such algorithms as dedicated methods.
The picture below shows the requirements related to the Printing document task (PDT) object type on different levels. I will discuss them later.
It is worth mentioning that while system requirements described all object types without exception, we didn't need to write use cases for all of them. Many of the object types represented lists of something (countries, months, time zones, etc.) and were used similarly. This allowed us to save our analysts’ time.
An interesting question is which stakeholders and project team members use which requirement level. Future end users can read general scenarios, but use cases are too complicated for them. Because of this, our analysts just discussed them with end users and didn’t ask them to read or review use cases. Programmers usually need algorithms, checks and system requirements. You definitely can respect a programmer who reads use cases. Test engineers need all levels of requirements, since they test the system at all levels.
In comparison with, for example, MS Word documents that are still widely used, Wiki allowed our requirements to be changed by several team members at the same time. Note at the same moment different parts of requirements were in different states: from ‘In Work’ to ‘Implemented’.
Let’s talk about general scenarios, the topmost requirement level.
The root page of our requirement tree consisted of general scenarios, each of which described one of 24 business processes to be implemented in this ERP module. The scenarios were located on the page in the order in which they occurred in the company's actual workflow: from creating a shipment with sold goods to delivering it to a client. Some specific or side scenarios were placed on separate pages.
A general scenario is a sequence of steps taken by the user and by the system to achieve a specific goal. It provides an outline, a summary of the company's process and does not have to mention every detail, they are described in use cases related to this scenario. Our general scenarios contained steps performed not only inside the system, but also outside it to show users’ work in its entirety, with the complete sequence of steps necessary to achieve the business goal.
Other reasons why we need general scenarios are:
- consolidating knowledge about the company's business processes
- having an agreement on business processes with future users
- being sure that requirements are complete, nothing is missing
- having the starting point for looking for a particular use case or algorithm
Below is a business scenario example:
Printing documents, performed by Clients department specialist
The purpose is to print and deliver the client's shipment documents
- The user performs batch printing of shipment documents, i.e., chooses a Printing document task from the list by a shipment number, changes its state to 'In Work' and prints the documents.
- The user checks that documents have been printed correctly and changes the Selection task's state to 'Ready'.
- If the company should deliver the shipment to the client, CDS puts the prepared documents in separate piles depending on city or regional delivery.
- If the client picks up the shipment itself, CDS gives the prepared documents to the client's representative.
As you can see, only half of the steps are performed in the system. Also, it seems that we could simplify the work of the user if at the second step, the system, not the user, would change the Selection task’s state to ‘Ready’.
At the first step, the link ‘Printing document task’ pointing to the object type in system requirements is superfluous since no one who reads a general scenario wants to read details about an object type. At the same time, the link ‘batch printing of shipment document’ is very important because it leads to the use case that formally describes the user's and the system's actions.
Let’s go down one level, to use cases, and see what is written in the 'Batch printing of shipment documents' use case.
Our use cases had the following template:
- Header with the following fields
- State, one of these: In Work, Ready to Review, Approved
- Business roles (of the users that interact with the system in this scenario)
- Minimal Guarantees
- Success Guarantees
- Link to the UI screen form (designed by a UI designer)
- Link to the test case (added by a test engineer)
- Main scenario
- Possible extensions to the main scenario
Use cases consisted of numbered steps of actions, each of which usually began with the word 'User' or 'System', because as you remember, there are only two actors. Numbering was important because it allowed us to refer to a particular step in questions and comments related to this use case. Each step is a simple sentence in the present tense.
And now, here is the use case the 'Printing documents' general scenario above refers to.
Batch printing of shipment documents
Users: Clients department specialist
Goal: Have shipment documents printed
Preconditions: UI shows the list of printing document tasks
Minimal Guarantees: Printing document task is still in the 'Created' status
Success Guarantees: Shipment documents have been printed, the printing document task moved to the 'Ready' status.
UI: Batch printing for N shipments
Checklist: Batch printing of shipment documents (checklist)
- User selects the printing document task (PDT) in the 'Created' status and presses the 'In Work' button.
- System changes PDT's status to 'In Work' according to Algorithm for changing PDT's status to 'In Work' and refreshes the list.
- User presses the 'Batch printing' button.
- System shows the 'Batch printing for N shipments' screen
- User selects a Kit.
- System shows the Kit's specification.
- User makes sure that the number of documents in the Kit is correct, corrects it if necessary, presses the 'Print' button
- System runs the Algorithm for creating documents for batch printing and prints created documents
- User makes sure all necessary documents are printed and presses the 'To Ready' button
- System changes PDT's status to 'Done' according to Algorithm for changing PDT's status to 'Done' and refreshes the list.
As you see, the scenario is very detailed, in fact, it can be used as a test case. It follows all the rules on how to write requirements: each requirement should describe only one thing and be unambiguous, its start and end are clearly defined, it doesn’t contradict other requirements, it is written because users need it, it can be implemented and verified.
Sometimes analysts create screen forms and write use cases based on them, explaining this by the fact that this is clearer. There is some truth in it, but we held the position that the user interface is the business of a UI designer. First, an analyst describes what should happen, and then a UI designer draws a sketch of a screen form or a Web page. Sometimes, after that we had to change the use case. This is nothing to worry about, because our aim is to design all parts of the systems so that users could do their work in the most convenient way. And each member of the project team, whether an analyst or a UI designer, having specific knowledge and contributing to the common cause, has an impact on the work of other members of the project team. Only together, joining forces, we can achieve an excellent result.
As you can see, our scenario refers to three algorithms: Algorithm for changing PDT's status to 'In Work', Algorithm for creating documents for batch printing and Algorithm for changing PDT's status to 'Done'. The first of them is shown in the next section.
Algorithms and Checks
When we wrote algorithms, we ran into a funny problem. Analysts tried to include all possible checks. However, the result was very poorly readable and, as a rule, some details were missed anyway (probably this happened because there was no compiler -). Therefore, we decided that the analyst should describe only what is important for the business logic, the programmer who would implement it had to provide all necessary checks in the code. This is a kind of challenge for programmers, but I’m sure that a project team can create a solid system only if all of them, including programmers and test engineers, know about the business processes they are implementing. Having good written requirements is one thing, but even this doesn’t allow programmers to be ignorant of what the system does in general and how it helps its users in their business.
Here is our algorithm.
Algorithm for changing PDT's status to 'In Work'
- If PDT's status = 'Created' or 'Ready', System:
- Sets User = <current user>
- Sets Status = 'In Work'
- Recalculates Shipper's number
The algorithm indicates only one check but obviously when a programmer writes the related code, he/she should implement checks on input parameters, throw an exception if the current user is not defined, etc. Since algorithms of moving a PDT between different statuses are very similar, the programmer can decide to write one only non-public method for changing PDT's status to all possible values. In this case, at the API level, the same operations remain, but all of them call the same parameterized method. Choosing the best implementation of algorithms is a part of a programmer's work.
And finally, we go down to the deepest requirement level, at which all system objects are described in detail. We will see what our 'Print document task' object type consists of. This level is so deep that many books about requirements do not mention it at all.
It is well known that Programs = Algorithms + Data Structures. Thus, by and large, all that a programmer needs to know are the data structures and the algorithms that manipulate them.
We used the object-oriented approach and since it is based on the concepts of classes, our data structures are classes with fields and methods. Since the word 'class' is specific to programming here I use 'object type' instead.
Every object type description consisted of the following parts:
- Object type definition from the glossary
- Object type properties
- Object type operations and their permissions
- Data related to this object type
- Additional information
Let’s start from the second bullet since definitions were already discussed in the ‘Glossary’ section.
To describe object types, we used tables because they help to organize information, more visual, and easily extensible.
The first table included object type properties and their attributes that were necessary to create data structures in the database and implement this object type on the application server. We used the following attributes:
|Name || |
The object type name used by end users and project team members. For example: “I changed the bill number” - Number is a property of Bill. Throughout the documentation, we used references to object type properties as “
|Data Type || |
We used the following data types:
TimeSpan. Each data type had a reflection on all system levels: in the database as a DB field's type, on the application server as a type of C# programming language, and in the UI where there were several types of controls associated with each data type so that we could use, for instance, a dropdown or a combobox for
Enums. Each type had a definition so that programmers knew what to implement. For example,
Money was defined in this way: includes two parts - a positive or negative float number accurate to the 4th decimal place, and a currency, the Russian ruble by default.
|Editable || |
Yes or No depending on whether users can change an object type property value in the
Edit operation. If
No, this restriction was implemented on the application server and in the UI.
|Nullable || |
Yes or No depending on whether the property can have no value. For example, in our system, Boolean properties must have one of two possible values, but
String properties have
NULL by default. If
No, this restriction was implemented on the database level and on the application server.
|Unique || |
Yes or No depending on whether a property value is unique. Often, uniqueness was defined for a set of properties in this case
Yes+ was set for all of these properties. This restriction was implemented on the database level as a database index and on the application server.
|Comment || |
The property description: what it means, what it is for, how it is used. If the property value is calculated, the comment refers to the calculation algorithm.
In addition to these attributes, the table included two more columns that were filled by server-side programmers when they had implemented the object type:
- The object type property name in the code
- The database field name
Both of these attributes are optional, because, for example, the property of an object type may not be stored in the database but be calculated like a bill amount.
I want to highlight that programmers took part in requirement development. This is important for many reasons. Firstly, in this way, programmers were better aware of the requirements, moreover, the requirements became not just a piece of paper written by some analyst. Secondly, we had the API documentation at once. Thirdly, at any moment, we could clearly understand the current state of every object type's implementation, that became especially important when requirements were being modified. Of course, this methodology required programmers to be more disciplined, which in fact was a positive factor.
Also, because of these columns, programmers of different application levels could always understand the relation between the object type property in requirements, the related database field and the application server API.
As I already wrote, a table representation is very extensible. For example, for some object types for integration with a legacy system, we had an additional column where we described the data migration algorithm. Also, we used special icons to indicate how this property looks in the UI. At one time, we had a column with database index names so that programmers would not forget to create them for unique properties. If necessary, you can add more columns.
Here is the typical object type description in our system:
The second table described the object type's operations and related permissions. Every operation had a unique name on the application server (the Operation column) and a short menu name in UI (the Short name column). To perform an operation, the necessary permission should be granted to a user (the Permission column). For some operations, the Comments column described their algorithms or had a link to them. The API column was filled in by programmers. As in object type's descriptions, this was necessary to document the API, to increase the programmers’ involvement in requirement development and to visualize the current state of this object type's development. Here is an example:
The description of the PDT status
Enum was also here:
|Value ||Description ||Sort order ||Active ||XEnums.PrintTaskStatus |
|Created ||Initial status ||1 ||1 ||Created |
|In Work ||Somebody is working on the PDT ||2 ||1 ||InProgress |
|Ready ||PDT is finished ||3 ||1 ||Ready |
Here is a table of possible PDT's transitions between statuses, and corresponding operations:
|Source status \ Target status ||Created ||In Work ||Ready |
|Created ||- ||To Work ||- |
|In Work ||Cancel ||- ||To Ready |
|Ready ||- ||To Work ||- |
As you see, the PDT object type is simple, for complex cases, we created more sophisticated diagrams.
Right after deployment of the system, it should contain some data. For example, the administrative user, the list of currencies and countries and so on and so on. This information was described in the Data section.
Also, we had the Additional information section with relations between the object type and the legacy system that we migrated from.
Summing up, the system requirements for an object type included all information necessary to implement it: a database table structure and related constraints, a domain object’s fields and methods, all necessary algorithms, as well as deployment data. The structure of the description was easy to understand and extensible.
Many may say that such detailed requirements are time consuming and not necessary. But without them, how would a programmer know what exactly needs to be implemented? Is the phrase, “Please implement the User object type” enough to code? How much tea should be drunk with an analyst to get information about 40 (we had so many) properties of the object type User? Who, if not an analyst or designer, should make an effort to describe all object types?
At the bottom line, I want to say that the glossary and business roles, general scenarios, use cases, algorithms, checks, and system requirements, all together allowed us to implement and deploy a huge part of the ERP system in a short time. Yes, requirement management takes time and effort, but good requirements allow us to greatly decrease programming and testing time.
Tasks for Developers
Now, after I explained how our requirements were written, let me describe how we formulated tasks for server-side programmers.
Standard Task #1
Title: Implement the object type <object type name>
Description: The link to the wiki page with the object type's system requirements
A programmer should do the following:
- Create database objects (tables, primary and foreign keys, triggers, etc.)
- Implement the domain object (as well as internal application server classes like a repository, service, etc.)
- Write the code to populate the database with initial data
Everything above can be done based on the link provided. The programmer should update the object type's property table by providing database table field names and object type property API names.
Standard Task #2
Title: Implement operations and necessary permissions of the object type <object type name>
Description: The link to the wiki page with the object type's system requirements
On the wiki page, a programmer can find the operation's names, permissions and in the Comment column necessary to implement algorithms and checks.
Standard Task #3
Title: Change the object type <object type name> and its operations
We created tasks of this type if we had changed this object type's requirements. The task description included the description of the changes or just a link to a version comparison page.
The Tool to Manage Requirements
It was Atlassian Confluence that helped us to successfully implement requirement development and management. In this last section, I want to point out several of its important advantages:
- Possibility of remote work
- Links. As you saw above, links allowed us to have different pieces of the requirements on different pages and at the same time, they were connected to each other and together formed the single document.
- Notifications when something is changed. This is one of the most important instruments for team work. For example, having received such a notification the project manager can create tasks for developers and test engineers know that it is necessary to update checklists.
- Comments. Many of our pages had a big number of comments. It is very convenient to work with comments in Confluence because it supports threads. Also, you can use its rich text editor to highlight something in your comment.
- The powerful rich text editor.
- Page history, ability to compare different versions, rollback to an old version.
- Ability to view and reorganize pages in the tree hierarchy.
Of course, we also ran into several problems:
- As soon as the whole documentation uses the same names of object types and their properties, it would be very useful to have a tool to automatically change the changed name across all the pages.
- No statistics. For example, every page had a status. But we couldn’t automatically collect statuses of all pages and have a picture of the current requirement development state.
- We had to create complex diagrams in another application, save them in PNG format and add them to a Confluence page. Besides that, it was necessary to attach the source file for further modifications.
- I failed to find a way to export a page hierarchy to MS Word. Export to XML and PDF had glitches. I hope Atlassian has already fixed these issues.
- It is not possible to collapse comment threads. This becomes necessary when you want to somehow hide a thread with a big number of comments because the discussion is already finished.
- 5th May, 2020: Initial version