Separating storage from main contract in UUPS upgradeable contract

I've deployed a contract using the UUPS pattern and the file has become really long, so I want to refactor it and make it more modular and readable. In order to do so, I've decided to pull my storage variables out of the main contract and inherit them from another contract.

But I'm questioning whether I'm architecting this correctly. For simplicity, I've made some pseudo-code to explain my situation.

Version 1 of "Main" that's already deployed

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { EnternalInterface } from "./EnternalInterface.sol";

contract Main is Initializable, UUPSUpgradeable {
    string firstName;
    ExternalContractInterface externalContract;

    function initialize(string memory _firstName, address externalContractAddress) initializer public {
        __UUPSUpgradeable_init();
        firstName = _firstName;
        externalContract = EnternalInterface(externalContractAddress)
    }

    function getFirstName() public returns(string memory){
        return firstName;
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override
    {}
}

Me, one month later: ":thinking: now that this file is >300 lines of code, I should pull out the storage variables and create a "data holder" contract. I'll deploy a new implementation

Version 2 of "Main" (which inherits the data holder contract)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { DataHolder } from "./DataHolder.sol";
import { ExternalInterface } from "./ExternalInterface.sol";

contract Main is Initializable, UUPSUpgradeable, DataHolder {

    function getFirstName() public returns(string memory){
        return firstName;
    }

    function getExternalContractsAddress() public {
        return address(externalContract)
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override
    {}
}

Data holder contract

pragma solidity ^0.8.16;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ExternalInterface} from "./ExternalInterface.sol";

contract DataHolder is Initializable, UUPSUpgradeable {
    string firstName;
    ExternalInterface externalContract;

    function initialize(string memory _firstName, address externalContractAddress) initializer public {
        __UUPSUpgradeable_init();
        firstName = _firstName;
        externalContract = ExternalInterface(externalContractAddress)
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override
    {}
}

Questions:

  1. Whenever I want to abstract data out of a contract and have another contract inherit it - in the new contract, do I always need to create an initializer in the new contract? (it seems like if I don't, I'll lose upgradeability)
  2. In the DataHolder, if I don't use the initializer to set firstName and externalContract wouldn't I lose access to those variables? Or are they still stored in the proxy?

I'd love any other general feedback on how to separate storage from a main contract!