You probably don't really want or need to share all of the internal app data into the cloud. I recommend you take a look at CloudKit. You can save your binary assets that need to be shared between all users by placing the information into the public CloudKit database.
For getting started with CloudKit check out this full tutorial. Now the basics are covered here but how you use them most efficiently is not. You may want to consider only downloading assets when they are needed. To accomplish this it might be useful to create a table referencing the share status for owners of other records along with users who have access to those records (including an option for sharing to public ~ other app users).
You could then simply post a local alert to a user when new data is available and allow them to control the download (using a fetched count of new share records available to a user and then using a local notification). Or you might want to process downloads only in the background mode. This all depends on your use case, but you will want to think about when to make the action happen as that might cause unnecessary issues with user bandwidth. Maybe only download over wifi, etc...
I wrote this stuff a long time ago for a C++ application and made a post on my blog. Here's a quick summary (since the original article is quite long and contains file attachments):
In SQLite you write callbacks in raw C++:
Header file (file.h)
#include <iostream>
#include <sqlite3.h>
#include <cstdlib>
#include <assert.h>
void memberfunction(const unsigned char* text1,
const unsigned char* text2,
const unsigned char* text3);
void mcallback(sqlite3_context* context, int argc, sqlite3_value** argv);
It defines the two additional functions we'll need for the next step (actually our C++ side function and a SQLite stored procedure).
Implementation file (file.cpp)
This function will be called from our trigger from the DB.
void memeberfunction(const unsigned char* text1,
const unsigned char* text2,
const unsigned char* text3)
{
cout << "inserted: " << text1 << " " << text2 << " " << text3 << "n";
}
And now a SQLite stored procedure to call our function; a SQLite trigger will invoke this.
void mcallback(sqlite3_context* context, int argc, sqlite3_value** argv)
{
assert(argc == 3);
//SQLite types must be converted to C++ types
const unsigned char* text1 = sqlite3_value_text(argv[0]);
const unsigned char* text2 = sqlite3_value_text(argv[1]);
const unsigned char* text3 = sqlite3_value_text(argv[2]);
memeberfunction( text1, text2, text3);
}
We don't have to limit the function to just a simple print; this function could have (for example) moved data to another table.
Register the stored procedure to the DB
error = sqlite3_create_function(conn, "mcallback", 3, SQLITE_UTF8, NULL,
&mcallback, NULL, NULL);
Create the SQLite trigger
We need the stored procedure since triggers are built on top of them.
error = sqlite3_exec(conn, "create temp trigger callbacktrigger "
"after insert on hello for each row "
"begin "
"select mcallback(new.first_column, "
"new.second_column, new.exclamation_pt); "
"end;", 0, 0, 0);
The trigger is only temporary. This way it won't stick around in the database after execution terminates (of our whole program). Our callback won't be around after then anyway and the trigger could (and will, given it's nature) cause major problems if we use the database outside this application.
Best Answer
1. Full sync
I would recommend always developing a full sync. Even if you have a partial sync, it's not wrong to do a full sync once in a while to ensure that everything is still up to snuff.
Note that you could also not schedule a full sync but keep it manually triggerable by whoever supports the application.
2. Event based sync - First interpretation
I was a bit confused about your explanation here. Initially, I understood this as an event that was fired by B, so that you'd get an immediate update when a given item was updated in B.
The benefit of doing so is a very quick replication time, you are immediately aware of any changes. It also minimizes the data transfer size.
As a downside, event driven systems are often harder to debug.
You also run into the issue of what happens when the network connection between A and B is severed (or when A is offline) but B is still processing updates. The only way to work around this is B were to have a message queue where missed events would be stored until you confirmed having received them, but I surmise that you have no control over B's development.
This is why you want to have a full sync once in a while. It fixes any problems that may have occurred at one point or another.
So if this interpretation is correct; then you're correct that an event-driven partial sync would be better off with an infrequent full sync to fix unavoidable issues that may occur once in a while.
2. Event based sync - Second interpretation
But on a re-read, it started seeming more like B wasn't involved with the events. You're planning to do several sync operations, and want to chain these one after the other using events internally in your application.
There are many ways for you to achieve an orchestration of sync operations. Synchronous, asynchronous, scheduled, triggered, event driven, ...
But the focus of your question seems to be on maximizing data correctness while keeping network usage to a minimum. How you handle things internally in your application has no influence on that (barring any bugs in your code which again seems out of scope for the question at hand).
There is a third option here, but it would require B to implement this as well.
3. Differential sync.
The key difference between a differential sync and a full sync is that a differential sync only gives you the items that have been updated since the last time you synced.
This gives you the benefits of a full sync (i.e. you control when you receive the updates), but lowers network usage by removing items that need no update (since they haven't changed anyway).
Differential syncs can come in many different flavors:
This is the underlying principle on which versioning tools like Git or SVN are based. It dramatically lowers data usage (because an update usually only changes a small part of the whole), but requires more data handling logic compared to a full sync (which is a simple overwriting operation).
However, this requires B to develop the differential sync feature. You can't do that by yourself (unless you are currently already able to query on "last updated" fields).
Note
If items can be deleted on B's side, a differential sync can become an issue. If item A is not part of the differential sync data, that could mean either that A has been removed, or A simply hasn't been updated yet.
However, I expect most systems to implement a soft delete; which solves the issue as a soft delete is an update to the item.
If B does allow for hard deletes; then you can't use a differential sync (unless B is able to update you about deletions as well).