Low-Level Design: Multiform Application

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 application 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 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 organisation want to run all of the analytics to know the user behaviour and all. So you have to save these data considering this.

Low-Level Design

Now I will propose some idea 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 need an application health monitor (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 next component is Stage which is quite equivalent to a page/form.

If we look at a page very closely, we will notice user 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 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 behaviours 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. Initialise 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.javapackage com.opensource.stage_monitor.resume.contact_details;

import com.opensource.stage_monitor.resume.ResumeStageFactory;
import com.opensource.stage_monitor.resume.education_details.EducationDetailsStageManager;
import com.opensource.stage_monitor.resume.personal_detrails.PersonalDetails;
import com.opensource.stage_monitor.stage_manager.Stage;
import com.opensource.stage_monitor.stage_manager.StageManager;
import com.opensource.stage_monitor.stage_manager.StageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

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

Let's try to implement the Stage Monitor now and see how it will play with Stage Manager APIs to achieve its goals.

The basic task of the stage monitor is to keep information about the application state and how the user interacts with the application.

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 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.

Below I have implemented a ResumeMonitorManager:

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

import com.opensource.stage_monitor.stage_manager.Stage;
import com.opensource.stage_monitor.stage_manager.StageManager;
import com.opensource.stage_monitor.stage_monitor_manager.StageMonitor;
import com.opensource.stage_monitor.stage_monitor_manager.StageMonitorManager;
import com.opensource.stage_monitor.stage_monitor_manager.StageMonitorRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Optional;

@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…

Software Engineer at BookMyShow

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store