My solution is slightly different as I don't use my Bitcoin node's wallet functionality but hopefully it is of value to you anyways.
Whenever a new block is added to the chain my application fetches it from the node and scans it for spendable outputs. Any such outputs are written to the database, as well as the block's height and hash.
A fork means that one or more of the blocks that we previously processed are no longer part of the chain. My Bitcoin node (btcd) provides a notification hook for this specific event, but the same can be achieved with bitcoind's more general blocknotify.
When a fork occurs, we need to find the common ancestor and mark all previously processed blocks up to that point as invalid. To do this we step backwards through the (old) chain, starting at the last block in the database considered to be valid. For every block, we query the Bitcoin node to find out if it is valid, i.e. part of the new chain. If it is invalid, we mark it as such in the database and move on to its parent. If it is valid, we've found the common ancestor.
Then we query the node for the common ancestor's (new) child block, process it and move on to its child. We repeat this for the child until a block has no child, which means we have reached the end of the new chain and are synchronized with the network again.
In this way even larger forks are properly handled. Spendable outputs can be retrieved by the database at any time with a query such as:
SELECT txid, vout, value
FROM outputs INNER JOIN blocks ON outputs.block=blocks.hash
WHERE blocks.valid=true AND outputs.spent=FALSE;
I hope this helps. If something doesn't make sense yet, let me know and I'll try to clarify!