Learn how to write Functional Specification Documents
This is the CaseFu How to guide. It is a practical recipe describing how to write a Functional Specification Document.
On the top level, structure your FSD by business domains, e.g. customers, orders, invoices, etc.
Within each domain, the FSD in general consists of the following aspects:
While the Data model takes the least amount of work and time to write and read, it provides the most information value of all the parts of FSD. There is a saying that goes “Show me your data model and I’ll tell you what your system does.” It only exaggerates a bit and for some types of systems it is actually quite accurate.
You should therefore primarily strive to specify the data model, because it gives the reader the most bang for the buck. Also, if you want to spend minimum time on FSD but still have at least something, specify your data model.
Screen mockups have a balanced information/work ratio. They are a natural expansion on the data model as they provide more detailed information and context on how the system is used. Once you have completed the data model for the feature and want to be more precise in your specification, create screen mockups.
For systems that do not have UI but API instead (or a combination of these), an equivalent to screen mockups is a specification of inbound system interfaces.
Some features contain complex functionality which even the screen mockups are not adequate to convey. The best tool then are use cases. They are a standard, formalized way of specifying system functionality in the form of interaction of the user with the system. They are the most expensive way of writing FSD but at the same time are able to provide the most detail to the reader.
How to write the above artifacts is the subject of the following sections.
Before we delve into the details of the FSD parts, here are some tips on how to proceed in general.
Never, ever, try to write the whole FSD up-front, before the development even starts.
Always embrace the agile, iterative nature of software development. Write the specification for the features of the next development iteration, but not much more. You want to keep well ahead of the development to avoid becoming its bottleneck. On the other hand, you don’t want to disconnect from the development completely and build a pie in the sky. The development iterations give you important feedback on the content and quality of the FSD and keep your feet on the ground.
Iterative development also helps fundamentally with managing customer / product owner expectations. Once the iterative delivery rhythm sets in and the stakeholders see the system steadily emerging, the pressure to squeeze in everything and anything from the very beginning is relieved and that improves the overall relationships on the project and enables the FSD to become more focused.
Do not treat FSD as a “documentation” of the system that is written ex post. Surely, it also does serve the purpose of documenting system functionality for posteriority. The main purpose of FSD, however, is to be a tool for eliciting and understanding system requirements and for specifying them in the proper detail and form for development and testing. It therefore follows that FSD for a particular feature is always written before that feature is implemented, not after.
"Only ever implement what's in the FSD and nothing else." - make this the mantra of the development team. The developers must always follow the FSD and never stray away from it. If they have to implement different functionality, then the FSD must always be changed first. If they want to add something on top of what's in FSD, the FSD must be changed first.
This is the only way to ensure the FSD is always up-to-date. Specifying the functionality in the FSD must become a natural part of the development process, a step before the implementation itself.
Read the user requirements and look for nouns. The most obvious ones are your best candidates for entities.
Examples: Order, Customer, Address, Order item.
Once you have a couple of entities, find out what are the relationships between them. This includes deciding which entities do have a direct relation and which ones don't, and for each relation specifying its cardinalities.
The most common type of relation is 1:N, where for each 1 instance of a master entity there could be 0 to N instances of detail entity (e.g. each Order has exactly 1 Customer and each Customer may have 0 to N Orders).
Other cardinalities include 1:1 (Order - Address) and 1:1..N (Order - Order item, where each Order must have at least one item). The M:N cardinality is usually a temporary one and over time often evolves into an intermediary entity with two 1:N relations.
Given an entity, name the data items that the system should record for each instance of the entity.
Examples: for entity Order we want to keep Order number, Customer, Due date, Comment, Create time, etc.
For each identified attribute define its data type. Data types should ideally be aware of the target database, i.e. if you plan to run on Postgres in production, use data types supported by Postgres.
For attributes representing relations to other entities, the data type is either the related entity (in a logical data model) or the data type of the primary key of the related entity (for physical data model). This might sometimes lead to identifying a new entity.
Examples:
For each attribute define its status.
Is the value of the attribute always present (and the attribute can be made mandatory) or can it be missing sometimes (and the attribute must be optional)? Beware of optional attributes, they should be pretty exceptional, if you find your entity has many optional attributes, you probably have the entity wrong. Most likely it needs to be split into multiple entities.
Is the value of the attribute unique across all the instances of the entity (and the system can and should check for it)? Is it unique and also never changes (and therefore can be used as a natural key of the entity)? Is it a primary key? A foreign key?
Examples:
Strive to provide sample values for each attribute where possible or meaningful. An example is oftentimes very illustrative and may help the reader understand the attribute better.
Examples:
Many entities go through different states over time and allow different operations in each of them. The status of an instance may either reflect the development of the corresponding real world entity or a technical aspect of the instance within the system. If the entity has a field named "status", "enabled", "deleted", "sent" or similar, it's a good indication it has a lifecycle. Use the UML state chart diagram to capture the states and transitions between them.
Example: Order status attribute: created, submitted, sent, delivered, invoiced, paid.
Provide an estimate of the total number of instances of the entity, if applicable, or estimated increase per time period.
Examples:
Each entity from the data model will have a couple of screens to display and maintain its data. There will also be additional screens that might not be directly related to any particular entity, e.g. dashboards, reports, etc.
Examples:
Each screen will consist of forms and tables that present data to the user.
For each form define its fields and for each field define its status (read-only, optional, required), type of UI control (plain text, date picker, select, etc.) and an example value.
Similarly, for each table define its columns with example values and if the table is editable also the status and type of control of all the columns. The data should follow the data model where possible.
Examples:
Screens contain links and buttons that trigger system functionality and / or navigate between the screens. Specify all such links and buttons to establish navigation between screens and define the ways in which the user can activate system features.
Examples:
Specify functionality performed by the system on the user's action in the UI, these are typically buttons or screen loads.
Example: the Disable button on the Customer detail screen sets the Customer Status attribute to "disabled".
Strive to come up with a standardized screen flow for all entities. Rather than inventing new screen flow with every entity, sticking to a standardized one will not only simplify development, but also user experience. The following chart depicts a typical standardized full-fledged CRUDL screen flow for an entity.
Example:
Some entities would have a limited subset of the general screen flow, e.g an immutable entity won’t have the Edit screen, action and delete transitions and might even do without the Detail screen.
Identify and name the operations the API provides. You may want to start with CRUDL operations for the system entities.
If the target implementation technology is already decided, embellish each operation with information specific to it. E.g. for REST it might be beneficial to define the verb and URI template of each operation, for SOAP it might be the camel-cased code of the operation, etc.
For each operation define its request data structure. This comprises identifying the data attributes, their data types and example values, defining required and optional attributes and putting the attributes within an overall structure of the request data (a tree). The request data should follow the data model where possible.
Always include all input data of the operation, for example in REST some data might come in the body while other in the URL (e.g. id of the instance) and the headers (e.g. version of the instance in the "If-Match" header).
For each operation list its possible outcomes and their corresponding results. An outcome might be the default successful one (happy day), a variant of the successful outcome, a validation error, other types of errors, etc.
For each outcome define its result data structure. List the data attributes, their data types and example values and put them into the overall data structure (tree).
Include all result data for each outcome, for example for REST include the response status code and significant headers (e.g. ETag header for the version of the entity) along with the body.
An actor is a group of human users with specific goals in the system or an external application that calls the system. Actors usually end up being implemented as user roles.
Examples:
For each actor, list their use cases in the system. These are user-goal level, black box, system use cases, meaning each use case:
Examples:
Describe the main successful scenario of interaction of the primary actor with the system.
Each use case (potentially) consists of multiple scenarios how the interaction of the primary actor with the system can unfold, some successful (the use case goal is achieved) and some unsuccessful (the goal is not achieved). The use case format decidedly makes one of them stand out: a successful scenario that is either typical (happens most often) or, sometimes, the simplest one (has the least steps).
The main success scenario is a numbered sequence of steps where a “User” (the primary actor) interacts with the “System”. Each step is a sentence that begins with the word “User” or “System” followed by a verb. (The “user” here is the primary actor, i.e. either a human user using the UI of the “system” or an external application calling the API of the “system”).
The main success scenario is a single scenario, there is no branching there. Alternative scenarios are covered by the extensions. Beware of words “if” or “when” in the main success scenario, they might indicate improper use of branching there.
Example:
Use case: OR-100
Create order
Main success scenario:
Brainstorm all remaining scenarios of the use case, i.e. alternative ways in which the interaction of the user with the system can proceed.
List each one with a reference to the step of the main success scenario to which it relates. Since the steps of the main success scenario are numbered 1., 2., 3. etc., mark each extension with the number of the related step, followed by a letter (a, b, c, etc.) that distinguishes multiple extensions to the same step.
The extension specifies a condition which, when fulfilled, diverts the course of actions from the main success scenario into the extension. The extension condition is formulated as if the word “When” or “If” was at its beginning (although it is not explicitly used there) and is terminated with a colon “:”.
Examples: the above sample use case may have the following extensions:
For each extension specify the steps that take place when the extension’s condition is fulfilled.
Number each extension step with a prefix of the extension itself, followed by the number of the step within the extension, e.g. “2b3.” marks the 3rd. step of the extension 2a.
When an extension step has extensions (variant sub-scenarios) itself, nest these extensions within that step.
Examples:
Use case: CS-100
Create customer
Main success scenario:
Extensions: