I have been working on a project. I always followed this idea. Don't send all the data in one call.
Here is an example,
Suppose there is an API to return all the list of students that can be added to test they need to finish.
So, on UI side every student have one button "add" which will show a pop up if the student is already assigned to take the test. Or it will show a pop up he has already finished the test.
I could join many table and send all the data in one api call while fetchig students. Or
I could send the send the students and then on "add" there is another API to make sure the above mentioned conditioned met.
Which approach is better?
Because If I send all the data in one api call, there might be only few students be assigned the test.
Checking if a student is already assigned or not should happen in the backend, not frontend, and also atomically so as to prevent duplicates - either using a database transaction or a unique constraint.
When the Add button is clicked then in any case a backend call will need to be made (to perform the actual Add). If the add failed, the backend can interpret the "unique constraint violation" database error and return a "student is already assigned" message.
For the rest of the question, the rule is simply: don't fetch more data than is required by the UI.
If the Add button is always shown regardless of whether or not the student is already added, there is no need to retrieve this information beforehand.
But it might be useful to give a visual indication of which students are already added, in that case obviously there's no choice but to retrieve and return this information to the UI.
Fortunately GraphQL is precisely the tool for this job - it makes it possible for the UI to request exactly what information is needed for a given page, without having to code each and every possible query by hand.
Related
Background
Let us consider a hypothetical scenario. Suppose we have five different tables in our database,
Customers
Categories
Products
Orders
OderDetails
Our client wants us to add a search bar to the frontend, where a user can search for a specific product and upon tapping the search button, matching products has to be displayed on the frontend.
My Approach for Tackling This Problem
In order to add the aforementioned functionality, I came across the following strategy.
π I would add an input box to ender the product name and a submit button.
π Upon pressing the submit button, a GET request will be sent to the backend. On the query parameters, the product name that has been entered by the user will be included
π Once the GET request is received to the backend, I will pass it down to the ProductsController and from within a method defined inside the ProductController, I will use the Product model to query the products table to see if there are any matching results.
π If there are any matching results, I will send them to the frontend inside a JSON object and if there aren't any matching results, I will set a success flag to false inside the JSON object and send it to the frontend
π In the frontend, if there are any matching results, I will display them on the screen. Else, I will display "No Results Found!"
Problem with My Approach
Everything works fine if we only want to search the products table. But what if, later our client tells us something like "Remember that search functionality you added for the products? I thought that functionality should be added to orders as well. I think as the list of orders by a user grows and grows, they should be able to search for their previous orders too."
π Now, since in our previous approach to search products was implemented in the ProductController using the Product model, when we are adding the same functionality to Orders, WE WOULD HAVE TO DO THE SAME THINGS WE DID IN THE ProductsController AGAIN INSIDE THE OrdersController USING THE Order model. It does not take seconds to understand that this leads to duplication of code.
Problem Summary
β How do we add an API endpoint in laravel for a search functionality that can be used to search any table in our database and get results, instead of the search functionality being scoped to a specific controller and a corresponding model?
A good start would be to create a trait called something like searchable and add it to all the models you want to search and put any shared logic between the different models in there. Possibly you'd want to manually allow different columns so in each model you have an array of searchable columns which you'd refer to in your trait.
Your controller would have to point to the related model and call a method in the trait that searches.
As others have pointed out this is a high level question so I won't go too much in detail. Of course there are a million ways to implement this, just try and keep shared logic in place.
i am using Spring/Hibernate/ZK. In one tab i get object from DB for editing by user, but second user can open the same tab and the same object for editing . I want to informed second user whit message like "This object is Π°lready open" and hide buttons for save.Π’hus second user can see current data from DB to this object but can`t edint him.Is there a way to check session for this object or another way to do that.
The other answers mostly look at the database, but if all users use the same zk application to access the database, you could keep track of opened objects in the Composer or ViewModel (depending whether you use MVC or MVVM; I'll just call it controller).
Your controller would need a static list of objects that are currently modified. If a user requests to open an object that is not in the list, everything is fine and your controller enables the fields and save button. Otherwise, those are disabled and/or you display a message.
The tricky part is clearing objects from that list. If a user presses the save button, you just remove the object from the list. But what if the user doesn't and just closes the tab or their session just times out? In this case you need a callback, or a mechanism that regularly checks whether the screen is still open.
You could achieve this by adding a zk timer to the tab that pings every now and then and updates the timestamp in your static list (so make it a map). If a new user tries to edit the object, check how old the last timestamp is. If it is old enough (i.e. the previous user saved it or abandoned the screen), allow them to edit it.
Still, you have to think about what to do if a user just keeps the screen open. How long are they allowed to keep the lock on the object? This is an issue in Microsoft Office as well. If multiple users try to open an Excel file from a network location, the first one gets to lock and the others cannot save until that user saves.
You may have additional field which indicates that column is being edited. When first user starts work, the field would be updated. The second user would query object with 'on hold' status and your code would handle this.
Other way - use Hibernate #Version field in your entity. It holds object version which is incremented after every update operation. If second user would save object after first one already saved, it would throw OptimisticLockException which you could handle in your code. More about optimistic and pesimistic locking: Chapter 5. Locking. Related discussions: Hibernate Automatic Versioning and When to use #Version and #Audited in Hibernate?
The best solution is to use Optimistic Concurrency Control with Versioning and when Hibernate throws Concurrency Update issue due to same row is being updated in two transaction then use one of below strategy
First Wins Strategy
Last Wins Strategy
Merge Conflicting Update Strategy
First Wins Strategy is not good solution as it leads to lost update and user will get frustrated that all his work is lost.
By Last Wins Strategy one of user will get error message that you are working on Stale data and start your transaction again . By this way also user can get frustrated due to fact that now again he need to restart operation from beginning but his changes will not lost.
Instead go with Merge conflicting Update Strategy, when Hibernate throws Stale object exception reload screen with new data and user will see updated result and allow him to proceed with latest data. In this user changes will not loss and user will not get error message , just his screen reloads with fresh data and he can decide whether to proceed or not .
You can take example any e-commerce site and you will get one of result of either Last Wins Strategy or Merge Conflicting Update Strategy. Two user can start to by one item but one of user will get message in last screen that item is not stock.
I implementing RESTful API service and i have a question about saving related records.
For example i have users table and related user_emails table. User emails should be unique.
On client side i have a form with user data fields and a number of user_email fields (user can add any number of fields independently). When the user saves the form i must first make query to create record in users table to get her ID, ββand only then i can make query to save user emails (because in now i have id of record which come with response after saving user data). But if user enters not unique email in any field then the request will fail. So I create a record in the users table but not create record in user_emails table.
What are the approaches to implement validation of all this data before saving?
This is nor related restful api but transactional processing on the backend. If you are using Java, with JPA you can persist both element in the same transaction then you can notice if there is a problem and rollback the entire transaction returning a response.
I would condense it down to a single request, if you could. Just for performance's sake, if nothing else. Use the user_email as your key, and have the request return some sort of status result: if the user_email is unique, it'll respond with a success message. Otherwise, it'd indicate failure.
It's much better to implement that check solely on the server side and not both with the ID value unless you need to. It'll offer better performance to do that, and it'll let you change your implementation later more easily.
As for the actual code you use, since I'm not one hundred percent on what you're actually asking, you could use a MERGE if you're using SQL Server. That'd make it a bit easier to import the user's email and let the database worry about duplicates.
There is custom field "Lock Flag" in Account BC, namely in S_ORG_EXT_X table. This field is made available in Opportunity BC using join to above table. The join specification is as follows: Opportunity.Account Id = Account.Id. Account Id is always populated when creating new opportunity. The requirement is that for newly created records in Opportunity BC if "Lock Flag" is equal to 'Y', then we should not allow to create the record and we should show custom error message.
My initial proposal was to use a Runtime Event that is calling Data Validation Manager business service where validation rule is evaluated and error message shown. Assuming that we have to decide whether to write record or not, the logic should be placed in PreWriteRecord event handler as long as WriteRecord have row already commited to database.
The main problem was how to determine if it is new record or updated one. We have WriteRecordNew and WriteRecordUpdated runtime events but they are fired after record is actually written so it doesn't prevent user from saving record. My next approach was to use eScript: write custom code in BusComp_PreWriteRecord server script and call BC's method IsNewRecordPending to determine if it is new record, then check the flag and show error message if needed.
But unfortunately I am faced with another problem. That joined field "Lock Flag" is not populated for newly created opportunity records. Remember we are talking about BC Opportunity and field is placed in S_ORG_EXT_X table. When we create new opportunity we pick account that it belongs to. So it reproduceable: OpportunityBC.GetFieldValue("Lock Flag") returns null for newly created record and returns correct value for the records that was saved previously. For newly created opportunities we have to re-query BC to see "Lock Flag" populated. I have found several documents including Oracle's recomendation to use PreDefaultValue property if we want to display joined field value immediately after record creation. The most suitable expression that I've found was Parent: BCName.FieldName but it is not the case, because active BO is Opportunity and Opportunity BC is the primary one.
Thanks for your patience if you read up to here and finally come my questions:
Is there any way to handle PreWrite event and determine if it is new record or not, without using eScript and BC.IsNewRecordPending method?
How to get value of joined field for newly created record especially in PreWriteRecord event handler?
It is Siebel 8.1
UPDATE: I have found an answer for the first part of my question. Now it seems so simple to me that I am wondering how I haven't done it initially. Here is the solution.
Create Runtime Event triggered on PreWriteRecord. Specify call to Data Validation Manager business service.
In DVM create a ruleset and a rule where condition is
NOT(BCHasRows("Opportunity", "Opportunity", "[Id]='"+[Id]+"'", "AllView"))
That's it. We are searching for record wth the same Row Id. If it is new record there should't be anything in database yet (remember that we are in PreWriteRecord handler) and function returns FALSE. If we are updating some row then we get TRUE. Reversing result with NOT we make DVM raise an error for new records.
As for second part of my question credits goes to #RanjithR who proposed to use PickMap to populate joined field (see below). I have checked that method and it works fine at least when you have appropriate PickMap.
We Siebel developers have used scripting to correctly determine if record is new. One non scripting way you could try is to use RuntimeEvents to set a profileattribute during the BusComp NewRecord event, then check that in the PreWrite event to see if the record is new. However, there is always a chance that user might undo a record, those scenarios are tricky.
Another option, try invokine the BC Method:IsNewRecordPending from RunTime event. I havent tried this.
For the second part of the query, I think you could easily solve your problem using a PickMap.
On Opportunity BC, when your pick Account, just add one more pickmap to pick the Locked flag from Account and set it to the corresponding field on Opportunity BC. When the user picks the Account, he will also pick the lock flag, and your script will work in PreWriteRecord.
May I suggest another solution, again, I haven't tried it.
When new records are created, the field ModificationNumber will be set to 0. Every time you modify it, the ModificationNumber will increment by 1.
Set a DataValidationManager ruleset, trigger it from PreSetFieldValue event of Account field on Opportunity BC. Check for the LockFlag = Y AND (ModificationNumber IS NULL OR ModificationNumber = 0)) and throw error. DVM should throw error when new records are created.
Again, best practices say don't use the ModNumbers. You could set a ProfileAttribute to signal NewRecord, then use that attribute in the DVM. But please remember to clear the value of ProfileAttribute in WriteRecord and UndoRecord.
Let us know how it went !
If making things work is only requirement, we can put all controlling login and DB handling logic even in the views & it will work. However this is not a right approach for reusable design.
Before I ask my real design question, below is my current understanding about separation of responsibilities in terms of model.
All Database related code, even db related logic, should go in models.
For a table, say 'my_tab', propel generate 4 classes, out of which only 2 classes 'MyTab.php' and 'MyTabPeer.php' should be edited.
MyTabPeer.php must only have data fetching.
Any logic, if required to fetch data, should go in 'MyTab.php'
This is simple and I hope it is correct, if not, please correct me.
Now, I have a special condition. I've 4 tables, say a, b, c, d. For that, propel generated 8 editable classes (excluding base*.php)
A.php APeer.php B.php BPeer.php
C.php CPeer.php D.php DPeer.php
One page of my application, shows Mailbox (say). Mailbox is not a table in database but it gets its data from complex join query between above 4 tables along with lot of calculation/conditions.
I generated that query, fetch data from it and displayed it. Mailbox is running as expected. However I did it in my controller (action class), which I know is not a right place for that.
My question is, where should I put that code? Possible options:
Controller, I think, is not a right place for DB logic/fetch.
I've 8 model classed however data do not belong to any one of them but as combination of them all.
A separate helper/lib, but I know I'll never reuse that code as its unique page of the site.
Anywhere else?
Please suggest if I'm wrong but I guess I should put it in models as it is fetching data. Since A is primary table, I probably should put code in A.php and APeer.php. If that is correct place, next question is, What should go in A.php & what should go in APeer.php? I've following operations to do:
Some logic to decide what columns, should I select.
As like mailbox, I can show received/sent message. Controller will tell what to show but there are some db logic to set conditions.
Then really fetch data from complex Join query.
Returned data will have all rows but I might need to merge few rows conditionally.
As per my understanding, Point 3 should go in APeer.php and rest in A.php. Is my understanding correct?
You should create separate model class i.e. Mailbox.
Method of this model should do the complex select and return data to your action in controller. This solution will not break MVC approach.