How to use condition and multiple steps in a reactive function - mutiny

I'm currently building a reactive app using kotlin quarkus and mutiny, being new to reactive programming i wonder what's the best way to handle the following workflow :
try to find an item in the database (dynamodb)
if it exists return a value indicating this to the caller
if it does not exists call the save service
call an external service to get some data
update the item in database with those data from the external service
return a value indicating that the object has been created
Here's my code for now :
fun createCard(creationOrder: CreationOrder): Uni<CardCreationResult> {
return creationOrdersRepository.findByOrderId(creationOrder.orderId)
.onItem().transform {item ->
if (item != null) {
CardCreationResult.AlreadyCreated
} else {
creationOrdersRepository.save(creationOrder)
//TODO external webservice call
val cardNumber = UUID.randomUUID().toString()
creationOrdersRepository.updateCardNumberAndStatus(externalServiceCallResult)
CardCreationResult.Created
}
}
}
This method will eventually be called by a rest endpoint.
creationOrdersRepository.save and creationOrdersRepository.updateCardNumberAndStatus returns a CompletableFuture (i'm using the quarkus amazon dynamodb client).
Is this the right way to do it ? Should i wrap the save and updateCardNumberAndStatus results in Uni (i have been trying to but keep getting type error) ?

I don't believe your code does what you expect. If I understand correctly you need to "save" and "update" before emitting your result. So, you need something like (in Java, as my Kotlin is not great):
return creationOrdersRepository.findByOrderId(creationOrder.orderId)
// We will be returning a Uni as the save, update and the web service are
// async
.onItem().transformToUni(item -> {
if (item != null) {
return Uni.createFrom().item(CardCreationResult.AlreadyCreated);
} else {
// Create an Uni from the save operation
Uni<Void> save = Uni.createFrom().completionStage(() ->
creationOrdersRepository.save(creationOrder));
// This uni represents the invocation of the remote service
Uni<String> webService = callWebService();
// we chain the operations save -> web service -> update -> Created
return save
.chain(webService)
.chain(externalServiceCallResult ->
Uni.createFrom().completionStage(() -> creationOrdersRepository
.updateCardNumberAndStatus(externalServiceCallResult)
)
.replaceWith(CardCreationResult.Created);
}

Related

I don't understand the order that code executes when calling onAppear

I have been getting this problem now a few times when I'm coding and I think I just don't understand the way SwiftUI execute the order of the code.
I have a method in my context model that gets data from Firebase that I call in .onAppear. But the method doesn't execute the last line in the method after running the whole for loop.
And when I set breakpoints on different places it seems that the code first is just run through without making the for loop and then it returns to the method again and then does one run of the for loop and then it jumps to some other strange place and then back to the method again...
I guess I just don't get it?
Has it something to do with main/background thread? Can you help me?
Here is my code.
Part of my UI-view that calls the method getTeachersAndCoursesInSchool
VStack {
//Title
Text("Settings")
.font(.title)
Spacer()
NavigationView {
VStack {
NavigationLink {
ManageCourses()
.onAppear {
model.getTeachersAndCoursesInSchool()
}
} label: {
ZStack {
// ...
}
}
}
}
}
Here is the for-loop of my method:
//Get a reference to the teacher list of the school
let teachersInSchool = schoolColl.document("TeacherList")
//Get teacherlist document data
teachersInSchool.getDocument { docSnapshot, docError in
if docError == nil && docSnapshot != nil {
//Create temporary modelArr to append teachermodel to
var tempTeacherAndCoursesInSchoolArr = [TeacherModel]()
//Loop through all FB teachers collections in local array and get their teacherData
for name in teachersInSchoolArr {
//Get reference to each teachers data document and get the document data
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument {
teacherDataSnapshot, teacherDataError in
//check for error in getting snapshot
if teacherDataError == nil {
//Load teacher data from FB
//check for snapshot is not nil
if let teacherDataSnapshot = teacherDataSnapshot {
do {
//Set local variable to teacher data
let teacherData: TeacherModel = try teacherDataSnapshot.data(as: TeacherModel.self)
//Append teacher to total contentmodel array of teacherdata
tempTeacherAndCoursesInSchoolArr.append(teacherData)
} catch {
//Handle error
}
}
} else {
//TODO: Error in loading data, handle error
}
}
}
//Assign all teacher and their courses to contentmodel data
self.teacherAndCoursesInSchool = tempTeacherAndCoursesInSchoolArr
} else {
//TODO: handle error in fetching teacher Data
}
}
The method assigns data correctly to the tempTeacherAndCoursesInSchoolArr but the method doesn't assign the tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool in the last line. Why doesn't it do that?
Most of Firebase's API calls are asynchronous: when you ask Firestore to fetch a document for you, it needs to communicate with the backend, and - even on a fast connection - that will take some time.
To deal with this, you can use two approaches: callbacks and async/await. Both work fine, but you might find that async/await is easier to read. If you're interested in the details, check out my blog post Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese.
In your code snippet, you use a completion handler for handling the documents that getDocuments returns once the asynchronous call returns:
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument { teacherDataSnapshot, teacherDataError in
// ...
}
However, the code for assigning tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool is outside of the completion handler, so it will be called before the completion handler is even called.
You can fix this in a couple of ways:
Use Swift's async/await for fetching the data, and then use a Task group (see Paul's excellent article about how they work) to fetch all the teachers' data in parallel, and aggregate them once all the data has been received.
You might also want to consider using a collection group query - it seems like your data is structure in a way that should make this possible.
Generally, iterating over the elements of a collection and performing Firestore queries for each of the elements is considered a bad practice as is drags down the performance of your app, since it will perform N+1 network requests when instead it could just send one single network request (using a collection group query).

Load Dropdown values dynamically in Appmaker

I have a use case where I need to display User's Team Drive names in a drop down, I have done a code to fetch team drive's names for a user but I am not able to load these values in Drop down dynamically.
In Drop down widget "options" tag I am making a function call, which calls server for fetching these data and server returns the values. But the same data is not being reflected.
Here's my snippet,
function fetchValues() {
google.script.run
.withFailureHandler(function(error) {
app.closeDialog();
setNotificationText(error);
app.popups.Snackbar.visible = true;
})
.withSuccessHandler(function(result) {
// This result has the list of team drive names.
return result;
})
.fetchValues();
}
Your client script function fetchValues returns undefined, you can test it using dev tools console:
console.log(fetchValues());
It happens due to async nature of communication between client (browser) and server (you can learn more here and here). The easiest and most straight-forward way to fix your current code will be changing your binding to this:
fetchValues(#widget);
and change your function to this
function fetchValues(dropdown) {
google.script.run
.withFailureHandler(function(error) {
...
})
.withSuccessHandler(function(result) {
dropdown.options = result;
})
.fetchValues();
}
But in general it doesn't seem to be a right way to populate dropdown's options and names in App Maker. I would consider reworking this part using Calculated Model or Custom Properties

DocumentDB select document at specific index

Is it possible to select a document at a specific index?
I have a document import process, I get a page of items from my data source (250 items at once) I then import these into DocumentDB in concurrently. Assuming I get an error inserting these items into DocumentDB I wont be sure what individual item or items failed. (I could work it out but don't want to). It would be easier to just Upsert all the items from the page again.
The items i'm inserting have an ascending id. So if i query DocumentDB (ordered by id) and select the id at position (count of all Id's - page size) I can start importing from that point forward again.
I know SKIP is not implemented, I want to check if there is another option?
You could try a bulk import stored procedure. The sproc creation code below is from Azure's github repo. This sproc will report back the number of docs created in the batch and continue trying to create docs in multiple batches if the sproc times out.
Since the sproc is ACID, you will have to retry from the beginning (or the last successful batch) if there are any exceptions thrown.
You could also change the createDocument function to upsertDocument if you just want to retry the entire batch process if any exception is thrown.
{
id: "bulkImportSproc",
body: function bulkImport(docs) {
var collection = getContext().getCollection();
var collectionLink = collection.getSelfLink();
// The count of imported docs, also used as current doc index.
var count = 0;
// Validate input.
if (!docs) throw new Error("The array is undefined or null.");
var docsLength = docs.length;
if (docsLength == 0) {
getContext().getResponse().setBody(0);
return;
}
// Call the CRUD API to create a document.
tryCreate(docs[count], callback);
// Note that there are 2 exit conditions:
// 1) The createDocument request was not accepted.
// In this case the callback will not be called, we just call setBody and we are done.
// 2) The callback was called docs.length times.
// In this case all documents were created and we don't need to call tryCreate anymore. Just call setBody and we are done.
function tryCreate(doc, callback) {
var isAccepted = collection.createDocument(collectionLink, doc, callback);
// If the request was accepted, callback will be called.
// Otherwise report current count back to the client,
// which will call the script again with remaining set of docs.
// This condition will happen when this stored procedure has been running too long
// and is about to get cancelled by the server. This will allow the calling client
// to resume this batch from the point we got to before isAccepted was set to false
if (!isAccepted) getContext().getResponse().setBody(count);
}
// This is called when collection.createDocument is done and the document has been persisted.
function callback(err, doc, options) {
if (err) throw err;
// One more document has been inserted, increment the count.
count++;
if (count >= docsLength) {
// If we have created all documents, we are done. Just set the response.
getContext().getResponse().setBody(count);
} else {
// Create next document.
tryCreate(docs[count], callback);
}
}
}
}

How can I check the number of calls to the database in LINQ query when using .NET Core and Code First?

I wrote the following code for one of my controller actions in an ASP.NET Core application and I want to know how many times the database was called. There are two LINQ statements, one that fetches the data and another that sorts the children. How does one confirm the number of database calls? I'm using the localdb that gets created by default in a Code First .NET Core application.
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var giftCard = await _context.GiftCards.Include(g=>g.Transactions).SingleOrDefaultAsync(m => m.Id == id);
if (giftCard == null)
{
return NotFound();
}
giftCard.Transactions = giftCard.Transactions.OrderBy(t => t.TransactionDate).ToList();
return View(giftCard);
}
Linq allows developers to build a query, which is executed once the full query is built. You can build a query using multiple expressions without making a single call to the database. Linq will delay the call until the last possible moment.
In your case, the statement if (giftCard == null) explicitly asks for the object. Therefore, a database call will be made to fetch the requested GiftCard. Secondly, The OrderBy operation will be performed on the transactions that are already in memory (due to Include(g=>g.Transactions)). So, no database calls here.
Ultimately, your whole code will make only a single database call.

Meteor GroundDB granularity for offline/online syncing

Let's say that two users do changes to the same document while offline, but in different sections of the document. If user 2 goes back online after user 1, will the changes made by user 1 be lost?
In my database, each row contains a JS object, and one property of this object is an array. This array is bound to a series of check-boxes on the interface. What I would like is that if two users do changes to those check-boxes, the latest change is kept for each check-box individually, based on the time the when the change was made, not the time when the syncing occurred. Is GroundDB the appropriate tool to achieve this? Is there any mean to add an event handler in which I can add some logic that would be triggered when syncing occurs, and that would take care of the merging ?
The short answer is "yes" none of the ground db versions have conflict resolution since the logic is custom depending on the behaviour of conflict resolution eg. if you want to automate or involve the user.
The old Ground DB simply relied on Meteor's conflict resolution (latest data to the server wins) I'm guessing you can see some issues with that depending on the order of when which client comes online.
Ground db II doesn't have method resume it's more or less just a way to cache data offline. It's observing on an observable source.
I guess you could create a middleware observer for GDB II - one that checks the local data before doing the update and update the client or/and call the server to update the server data. This way you would have a way to handle conflicts.
I think to remember writing some code that supported "deletedAt"/"updatedAt" for some types of conflict handling, but again a conflict handler should be custom for the most part. (opening the door for reusable conflict handlers might be useful)
Especially knowing when data is removed can be tricky if you don't "soft" delete via something like using a "deletedAt" entity.
The "rc" branch is currently grounddb-caching-2016 version "2.0.0-rc.4",
I was thinking about something like:
(mind it's not tested, written directly in SO)
// Create the grounded collection
foo = new Ground.Collection('test');
// Make it observe a source (it's aware of createdAt/updatedAt and
// removedAt entities)
foo.observeSource(bar.find());
bar.find() returns a cursor with a function observe our middleware should do the same. Let's create a createMiddleWare helper for it:
function createMiddleWare(source, middleware) {
const cursor = (typeof (source||{}).observe === 'function') ? source : source.find();
return {
observe: function(observerHandle) {
const sourceObserverHandle = cursor.observe({
added: doc => {
middleware.added.call(observerHandle, doc);
},
updated: (doc, oldDoc) => {
middleware.updated.call(observerHandle, doc, oldDoc);
},
removed: doc => {
middleware.removed.call(observerHandle, doc);
},
});
// Return stop handle
return sourceObserverHandle;
}
};
}
Usage:
foo = new Ground.Collection('test');
foo.observeSource(createMiddleware(bar.find(), {
added: function(doc) {
// just pass it through
this.added(doc);
},
updated: function(doc, oldDoc) {
const fooDoc = foo.findOne(doc._id);
// Example of a simple conflict handler:
if (fooDoc && doc.updatedAt < fooDoc.updatedAt) {
// Seems like the foo doc is newer? lets update the server...
// (we'll just use the regular bar, since thats the meteor
// collection and foo is the grounded data
bar.update(doc._id, fooDoc);
} else {
// pass through
this.updated(doc, oldDoc);
}
},
removed: function(doc) {
// again just pass through for now
this.removed(doc);
}
}));

Resources