Low-Level Design: Multiform Application

Sourav Kabiraj
6 min readFeb 13, 2021

This post is all about the low-level design of the multiform application. In my professional career, I have created a lot of software that comes under this umbrella. Hopefully, It will be helpful in your next LLD.

Overview

“What is the Multiform Application?”
Let start with an example. Say you want to create a sign-up flow for one of your applications which takes a lot of details (personal details, contact details, address, personal interests)from the user. To take those details you can easily create one big form and take all the details. To be very honest this kind of development is extremely easy for front-end and back-end devs. But for a user, it will be a nightmare. To deal with that problem most of the designers breaks this big form into multiple smaller form.

Sample sign-up flow

Problems

Now as a developer you have to deal with a lot of problems:
1. You have to create a hell of lot of endpoints (backend problem). Front-end devs have to create multiple forms/pages.
2. You have to keep a track of the user application path so that you can give the user an option to navigate back and forth in the application.
3. User can exit application flow anytime and come back later, At that time you want to show your user the page from where he left. And this is a challenging scenario.[Reopened application]
4.
After all this, your organization wants to run all of the analytics to know the user behavior and all. So you have to save these data considering this.

Low-Level Design

Now I will propose some ideas(a framework) to deal with the multiform application. This is completely my personal opinion. You are welcome to disagree with this and form your own opinion.

Let's start with the solution approach. I named it Stage Monitor.

Let's consider each form as an application Stage. [like personal details, contact details, address, personal interests]
We also need an application health monitor for each and every user (I named it StageMonitor) which will store application data as a whole. It wouldn’t be wrong if I call it the nucleus of our application.

It exposes all APIs that are needed to control an application. For example:

  1. Initialise API initiates an application.
  2. Proceed API submits a form/stage details and initiates next form/stage
  3. Get Stage For User API is used to fetch form details of a particular User
  4. Execute API is used to execute some commands on a certain form

Stage

Stage Model

Our first component is Stage which is quite equivalent to a page/form.

If we look at a page very closely, we will see users can do very few operations on it and it exactly looks like a doubly linked list node (it can have a previous page and next page).

For example: In the Sample sign-up flow image, the Address page has a previous page (i.e Contact Page) and a next page (i.e. Personal Interest Page).

Let's list down all the behaviors of page/form
1. Click on the Next Button.
2. Click on Back Button.
3. Run some kind of commands (say verify phone number/email Id etc.)

After seeing the previous list we can easily tell the APIs that a stage will expose:

  1. Initialize API initiates a stage (defines what happens when a user creates a new stage).
  2. Submit(Next) API submits form details
  3. Rollback (Back) API gives the ability to rollback or to go back to the previous page
  4. Get Stage API is used to fetch the current page details
  5. Execute API is used to execute some commands on the form

Let's create an interface to manage stage operations:

https://github1s.com/SouravKabiraj/stage-monitor/blob/HEAD/src/main/java/com/opensource/stage_monitor/stage_manager/Stage.javapublic class Stage<T> {    
private String id;
private String name;
private T data;
private String previousStageId;
private String nextStageId;
private Date createdAt;
private Date updatedAt;
public Stage(String name, String previousStageId) {
this.name = name;
this.previousStageId = previousStageId;
}
}
--------------------------------------------------------------------https://github1s.com/SouravKabiraj/stage-monitor/blob/HEAD/src/main/java/com/opensource/stage_monitor/stage_manager/StageManager.javapublic interface StageManager<T> {
// initiate will be called while use tries to open perticular page for first time
Stage<T> initiate(); // getStage return stage details with stage data. Optional<Stage<T>> getStage(String id); // submit save all data of current stage and moves to next page and initiate next stage Stage<T> submit(Stage<T> stage); // execute other kind of commends which doesn’t affect application progress. [ex. Send otp, verify email etc] Stage<T> execute(String cmd, Stage<T> stage);
}

Now you just have to implement all these methods for every stage.

Below I have implemented ContactDetailsStage:

https://github1s.com/SouravKabiraj/stage-monitor/blob/HEAD/src/main/java/com/opensource/stage_monitor/resume/contact_details/ContactDetailsStageManager.java

import java.util.Optional;

@Component
public class ContactDetailsStageManager implements StageManager<ContactDetails> {
@Autowired
StageRepository<ContactDetails> stageRepository;

@Autowired
EducationDetailsStageManager educationDetailsManager;

@Override
public Stage<ContactDetails> initiate() {
return stageRepository.save(new Stage<ContactDetails>("contact_details", null));
}

@Override
public Optional<Stage<ContactDetails>> getStage(String id) {
return stageRepository.findById(id);
}

@Override
public Stage<ContactDetails> submit(Stage<ContactDetails> contactDetailsStage) {
if (contactDetailsStage.getNextStageId() == null) {
Stage educationDetailsStage = educationDetailsManager.initiate();
contactDetailsStage.setNextStageId(educationDetailsStage.getId());
}
Stage<ContactDetails> savedPersonalDetailsStage = stageRepository.save(contactDetailsStage);
return savedPersonalDetailsStage;
}

@Override
public Stage<ContactDetails> execute(String cmd, Stage<ContactDetails> stage) {
return null;
}
}

StageMonitor

Stage Monitor

Our next and last component is StageMonitor. The basic task of the stage monitor is to keep information about the application state and how the user interacts with the application. There will be exactly one StageMonitor for every user which will hold all his application Stages.

First, list down all the task that the user wants to perform while they are felling an application:
1. Initialize the application for the current user.
2. Proceed to the next application page/stage.
3. Get the Last page/Stage of the application.
4. Execute some stage commands.

https://github1s.com/SouravKabiraj/stage-monitor/blob/HEAD/src/main/java/com/opensource/stage_monitor/stage_manager/StageManager.javapublic class StageMonitor {
public String id;
public String userId;
public String appName;
public String currentActiveStageId;
public List<Stage> stages;
}

Let’s create an interface to manage StageMonitor operations:

https://github1s.com/SouravKabiraj/stage-monitor/blob/HEAD/src/main/java/com/opensource/stage_monitor/stage_monitor_manager/StageMonitorManager.javapublic interface StageMonitorManager {     // Initialise Application
StageMonitor initiateFor(String userId, String appName);
// proceed to next stage after submitting current stage
StageMonitor proceed(String userId, String appName, Stage stage);
// getByUserIdAndAppName returns stage monitor by user and application Id
Optional<StageMonitor> getByUserIdAndAppName(String userId, String appName);
// execute runs stage specific commands
StageMonitor execute(StageMonitor stageMonitor, Stage stage, String cmd);
}

You have to implement this StageMonitorManager for your application and that's all. Expose StageMonitorManager methods as API endpoints so that frontend applications can consume.

Below I have implemented a ResumeMonitorManager:

https://github1s.com/SouravKabiraj/stage-monitor/blob/HEAD/src/main/java/com/opensource/stage_monitor/resume/ResumeManager.java

@Component
public class ResumeManager implements StageMonitorManager {
@Autowired
StageMonitorRepository stageMonitorRepository;

@Autowired
ResumeStageFactory resumeStageFactory;

@Override
public StageMonitor initiateFor(String userId, String appName) {
StageMonitor stageMonitor = new StageMonitor(userId, appName);
StageManager personalDetailsStageManager = resumeStageFactory.getStageManagerByName("personal_details");
Stage personalDetailsStage = personalDetailsStageManager.initiate();
stageMonitor.setCurrentActiveStageId(personalDetailsStage.getId());
stageMonitor.addStage(personalDetailsStage);
return stageMonitorRepository.save(stageMonitor);
}

@Override
public StageMonitor proceed(String userId, String appName, Stage stage) {
StageManager stageManager = resumeStageFactory.getStageManagerByName(stage.getName());
Stage submittedStage = stageManager.submit(stage);
StageMonitor fetchedStageMonitor = stageMonitorRepository.findByUserIdAndAppName(userId, appName).get();
fetchedStageMonitor.updateStage(submittedStage);
if (submittedStage.getNextStageId() != null) {
Optional<Stage> newlyCreatedStage = stageManager.getStage(submittedStage.getNextStageId());
if (newlyCreatedStage.isPresent()) {
Stage newStage = newlyCreatedStage.get();
newStage.setPreviousStageId(fetchedStageMonitor.getLastStageId());
fetchedStageMonitor.setCurrentActiveStageId(newStage.getId());
fetchedStageMonitor.addStage(newStage);
}
}
return stageMonitorRepository.save(fetchedStageMonitor);
}

@Override
public Optional<StageMonitor> getByUserIdAndAppName(String userId, String appName) {
Optional<StageMonitor> stageMonitorList = stageMonitorRepository.findByUserIdAndAppName(userId, appName);
return stageMonitorList.isEmpty() ? Optional.empty() : Optional.of(stageMonitorList.get());
}

@Override
public StageMonitor execute(StageMonitor stageMonitor, Stage stage, String cmd) {
StageManager stageManager = resumeStageFactory.getStageManagerByName(stage.getName());
stageManager.execute(cmd, stage);
return stageMonitor;
}
}

Please find the following link for Github link where I used this stage monitor to implement the resume builder application:
https://github.com/SouravKabiraj/stage-monitor

Conclusion

If you implement your next application using this design you have to create only 4 endpoints. Plus adding applications or new stages to an existing application is also easy. Just implement StageManager/StageMonitorManager and you are good to go…

--

--