An Implementation of eTOM Model as A User Account Management Database Design


In this article, I’m going to introduce a way of designing the user management system. I will represent those models by diagram or Prisma models.

Introduction to the eTOM Model

The classic eTOM model(Enhanced Telecom Operation Map) is a classic business process framework that is used widely by telecom service providers as an account manager system. This model describes some required business processes for the telecom business provides. Within all of its design ideas, I think its account system hierarchy model matches our exact needs. So I would like to introduce it and give a briefly implementation plan over this model.


The Common Requirements for A User Account Management System

The original eTOM model has 5 levels, but we only need 3 levels out of its original 5 levels to fulfill a user account management system. Currently for our universal account system we will need the following features. 

  1. Easy Migration over Current Account System Without Losing Any Data
  2. An Universal Account System with Extensibility over Permission Control that Fits Any User Roles.
  3. The Flexibility in User Relations and Permission, For Example, An Account Can Have Multi Roles and Different Permissions Granted For Each Role.

A Detailed Look at the Three Levels eTOM Account Model

First, let’s take a look at what the classic eTOM account model looks like. The three levels here means the customer, user, and account. In the eTOM model, these are just abstract concepts.

  • Customer: The highest level in this model. It points to an organization or entity.
  • User: This is the most interactive level among the eTOM model. This is the level that primary interact with the system.
  • Account: This is the lowest level in the eTOM model. In the eTOM concept, this is more like a virtual level that belongs to the user level.

Classic eTOM Model Diagram

The above diagram is the classic eTOM account model that fits the telegram industry. Let’s convert it into our own use case. See the below reorganized diagram.

Adjusted eTOM Model Diagram

In the original eTOM model, the customer to user is a many to one relationship: A customer can have multi users, but for a user, it only belongs one customer. That is not corrected in our case, a user might have multi customer even at the same time. Thus, I modified the relationship into a many-to-many relationship. However, we will be have trouble applying this model directly into our database. That because, for the original eTOM model, this model is implemented by multi level account system which means there are three separated accounts system exists at the same time. Here we will need to combine the three account system into one. 


A example of eTOM user management system under healthcare industry

In the example implementation below, I’m going to adapt it into a healthcare setting where Clinic will be the same as the company/organization setting, and the doctor will be the same as the user concept in the eTOM model, and patient is just like the account level in the eTOM model.

I already made a simple and classic user model class, just like any other account user system, it has username, password and contains basic user information. The original idea behind it is to implement a binary code that represents its user group and permission. But I think this is not very flexible and cannot easily show the relationship between users. For example, we know an account is customer level, but which customer is it, and maybe some customer have more permission over others. In this case, we might need to create a clinic level 2 for it. Below is the current user model code:

model user {
  user_id                          Int     @id @unique @default(autoincrement())
  username                         String  @unique
  password                         String
  twoFactorAuthenticationSecret    String?
  isTwoFactorAuthenticationEnabled Boolean @default(false)
  //Below Will be replace with a binary field in the future
  isUserInLabGroup                 Boolean @default(false)
  isUserInAdminGroup               Boolean @default(false)
  isUserInBillingGroup             Boolean @default(false)
  isUserInSupportGroup             Boolean @default(false)
  isUserInITGroup                  Boolean @default(false)
  isUserInShippingGroup            Boolean @default(false)
  isUserInDoctorGroup            Boolean @default(false)
  //Some User Information
  ...
}

The Idea of Virtual User

For the eTOM model, we now know that the original eTOM account system is for multi level account system. From higher ones to lower ones, the lower ones are more like a kind of extension over the higher ones. So, is there a way to turn this kind of level system into a simple model? And it’s clearly that a tree structure can represent this easily. So, here is my improved version on the user model.

model user {
  user_id                          Int     @id @unique @default(autoincrement())
  username                         String  @unique
  password                         String
  twoFactorAuthenticationSecret    String?
  isTwoFactorAuthenticationEnabled Boolean @default(false)
  
  // A Tree Like Struction
  node_id                          Int?
  leaf_id                          Int?
  node                             user?@relation("Tree", fields: [node_id], references: [user_id])
  leaf                             user?@relation("Tree")
  level                            String
  //Some User Information
  ...
}

Seems good, despite the self-relations might need to rework to reflect (many to many or one to many), we added two self-relations: node and leaf. But it does not solve the problem perfectly. We want to reflect that a user can have very different setting under different situations. For example, If I am a Doctor at Clinic A, Doctor Lee might have a phone number of 999-9999-9999, but in Clinic B, I will have a phone number of 111-1111-1111. In this case, we can treat Doctor Lee at Clinic A and Doctor Lee at Clinic B as two different users even if Doctor Lee is login to our system using the same account.

The below diagram shows the idea about the virtual user.

Now, we can implement the clinic, patients and doctors models in our database to continue our database design. Below are the clinic and doctor model for this example:

model clinic {
  clinic_id              Int                     @id @unique @default(autoincrement())
  clinic_name            String
  isActive               Boolean                 @default(true)
  clinic_address_group   address_group?
  clinic_contact_group   contact_group?
  clinic_reference_range reference_range_group[]
  clinic_address         address[]
  clinic_contact         contact[]
  customers              customer[]
  clinic_setting_group   setting_group[]
  clinic_setting_value   setting_value[]
}

model doctor {
  doctor_id                    Int                     @id @unique @default(autoincrement())
  doctor_first_name            String?
  doctor_last_name             String?
  doctor_middle_name           String?
  doctor_type_id               String?
  doctor_suffix                String?
  payment_method                 String?
  sales_id                       Int?
  isActive                       Boolean                 @default(true)
  doctor_address_group         address_group?
  doctor_contact_group         contact_group?
  orders                         order_info[]
  clinics                        clinic[]
  patients                       patient[]
  doctor_setting_group         setting_group[]
}

From the models, we can notice the many-to-many Clinic to Doctor and the Clinic to Patient relationship that I want to achieve in the adjust eTOM diagram has been already implemented. What we need to do is just to link the user to those models. Let’s see the below model:

model user {
  user_id                          Int     @id @unique @default(autoincrement())
  username                         String  @unique
  password                         String
  twoFactorAuthenticationSecret    String?
  isTwoFactorAuthenticationEnabled Boolean @default(false)
  
  // level:current user level-For example: "Patient", "Doctor","Clinic"
  level                            String
  // The list of ids of upper level users, use of self_relation
  upperlevel_users                 User[] @relation("UserRelation",reference:[user_id])
  lowerlever_users                 User[] @relation("UserRelation",reference:[user_id])
  //Some User Information
  ...
}

Since our account levels are fixed, we can represent it like below user model.

model user {
  user_id                          Int     @id @unique @default(autoincrement())
  username                         String  @unique
  password                         String
  twoFactorAuthenticationSecret    String?
  isTwoFactorAuthenticationEnabled Boolean @default(false)
  
  // level:current user level-For example: "Patient", "Doctor","Clinic"
  level                            String
  // The list of ids of upper level users, information models,(If other models are needed)
  clinics                          clinics[]
  customers                        customers[]
  //Sales-----Single Level Model
  sales                            sales[]
  //Some User Information
  ...
}

Right now, the relations are much more clearer. Since the patients level is the lowest level. If the level is patients and the clinic[] is null and there will be doctor[] that holds the patient(we can get the clinic for patient via the patient model here).There is no need to connect to patient model(there is no lower model than patient in the account system. So what about the extensibility? For example, right now for some other special model levels like sales, what if there are some other special accounts like blood_center? Let’s modify the sales model into a model called base_model and then we have:

model user {
  user_id                          Int     @id @unique @default(autoincrement())
  username                         String  @unique
  password                         String
  twoFactorAuthenticationSecret    String?
  isTwoFactorAuthenticationEnabled Boolean @default(false)
  
  // level:current user level-For example: "Patient", "Doctor","Clinic"
  level                            String
  // The list of ids of upper level users
  clinics                          clinics[]
  customers                        customers[]
  //Sales-----Single Level Model
  base_model                       base_model[]
  //Some User Information
  ...
}

For model base_model[](used to be sales)

model base_model {
  id            Int             @id @unique @default(autoincrement())
  firstname     String
  lastname      String
  isActive      Boolean         @default(true)
  address_group address_group?
  contact_group contact_group?
  address       address[]
  contact       contact[]
  setting_group setting_group[]
  setting_value setting_value[]
  
  type          String? @default("Sales")
  relation_to   String? @default("Doctor?")
  relation_id   String? @default("[1,2,3,4,5,6]")
}

We can represent the sales and other single level models as a plain model. And if it happens to have some relations we can used two strings here to connect instead of foreign keys(which cannot be added without model change). And since this is less likely to have a complex relation, the speed should not be a problem.(Of course, we will keep the sales model since it need some other functions, but we can have this. base model as plus.

After that our models will be like the below diagram:

Now, We Have Relations, What About Permissions?

In general, permission is just the same as the phone number I mentioned in the virtual user part of this article. We can implement it in a similar way like the below diagram:

For permission, we just need a simple model with two columns. See the model here:

model permissions {
  id            Int             @id @unique @default(autoincrement())
  permissions   String // For example, ['create_patient','delete_patient']
}

To reduce the string length, we can replace the create_patient with ‘1’ and store the information somewhere else.

And then, we can do the connection here. See the diagram below:

We just need to add a one to one relation between the user and the clinic. But this still not solve a problem: A doctor is likely to have different permission under different clinics. So, let’s make our permission model this way:

model permissions {
  id               Int             @id @unique @default(autoincrement())
  permissions      String
  //Newly Added
  permission_index Int64
  user_id          Int
  user             User           @relation(fields: [user_id], references: [user_id])
  }

Let’s make it into a three column by adding a permission_index. And here are the rules:

  • If a user is on its own, does not have upper level models:
    • permission_index = it’s own id
  • If a user has upper level models, for each upper permission:
    • permission_index = it’s own id + the upper level id
  • If a user has lower level models(haven’t added to user, but can be done by going the relationships using the linked model), for each lower permission:
    • permission_index = it’s own id – the lower level id (to prevent the lower level has the same id as the upper).

Since this is easily revertible using the permission_index and the levels we can easily know what this permission is on too.

Permission Change History

As for the permission change history, I will implement this at the permission change api. I will send the log over Fluentd. And from there, it can be stored into any log database. And we can have a tag for it and store this kind of information in a special database.

,

Leave a Reply

Your email address will not be published. Required fields are marked *