> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fusioncore.us/llms.txt
> Use this file to discover all available pages before exploring further.

# DB Classes

> Wrap per-object queries in db/ DB classes that declare their fields once and delegate to FCORE_BASE.CacheService, so every consumer in a transaction shares one cached query.

Rather than scattering inline SOQL through the codebase, each package wraps its queries in per-object **DB classes**, kept in a `db/` folder (for example `UserDB`, `AccountDB`). A DB class declares the fields it loads in one place and delegates to [`FCORE_BASE.CacheService`](/developer-guide/caching), so every consumer in a transaction shares one cached query.

Query through the DB class for an object whenever one exists. The conventional method shapes are `getById`, `getByIds`, and `getBy[Field]`.

## A Full DB Class (`UserDB`)

This is the actual `UserDB` class from the Platform package (`fusioncore-platform/force-app/main/default/classes/db/UserDB.cls`). It is declared `public inherited sharing`, declares the fields it queries in one private method, and delegates to `FCORE_BASE.CacheServiceWithoutSharing`:

```java theme={null}
/**
 * @description Retrieves User records from Salesforce DB
 */
public inherited sharing class UserDB {

    /**
     * @description Retrieves User record by provided Id
     *
     * @param id Id of the User record
     *
     * @return User record or NULL
     */
    public static User getById(Id id) {
        List<User> users = (List<User>) FCORE_BASE.CacheServiceWithoutSharing.getInstance().getByIds(
                User.SObjectType,
                new Set<Id>{id},
                getFieldsToQuery()
        );

        return users.isEmpty() ? null : users[0];
    }

    private static List<SObjectField> getFieldsToQuery() {
        return new List<SObjectField>{
                User.Id,
                User.Email
        };
    }
}
```

This is the canonical pattern:

* The fields are declared once in `getFieldsToQuery()` as a `List<SObjectField>`. To load another field, you add it here and every consumer picks it up.
* `getById` delegates to the cache's `getByIds` with a single-element `Set<Id>` and returns the first match, or `null` when nothing is found.
* Consumers never write inline SOQL for `User` — they call `UserDB.getById(...)`, and the cache ensures the query runs at most once per transaction.

## Singleton DB Classes for Small Tables

For small, frequently-read reference tables, the DB class is instead a **singleton** that loads every row once on construction and serves lookups from in-memory maps. This is the actual `CurrencyTypeDB` class (`fusioncore-platform/force-app/main/default/classes/db/CurrencyTypeDB.cls`). Note that it is `public inherited sharing` (not `global`), it caches all rows in its private constructor via `queryAndCacheAll()`, and it guards on `UserInfo.isMultiCurrencyOrganization()`:

```java theme={null}
/**
 * @description Helper class for Currency Types
 */
public inherited sharing class CurrencyTypeDB {

    // Singleton
    private static CurrencyTypeDB instance;

    // Cache
    private final Map<String, SObject> currencyIsoCodeToCurrencyType = new Map<String, SObject>();
    private SObject corporateCurrencyType;

    /**
     * @description Constructor
     */
    private CurrencyTypeDB() {
        queryAndCacheAll();
    }

    /**
     * @description Returns singleton instance
     *
     * @return CurrencyTypeDB instance
     */
    public static CurrencyTypeDB getInstance() {
        if(instance == null) {
            instance = new CurrencyTypeDB();
        }

        return instance;
    }

    /**
    * @description Retrieve All Currency Types and puts them into cache
    */
    private void queryAndCacheAll() {
        for(SObject currencyType : queryAll()) {
            currencyIsoCodeToCurrencyType.put(CurrencyTypeUtils.getIsoCode(currencyType), currencyType);

            if(CurrencyTypeUtils.isCorporate(currencyType)) {
                corporateCurrencyType = currencyType;
            }
        }
    }

    /**
    * @description Retrieve All Currency Types
    *
    * @return List of Currency Type records
    */
    private static List<SObject> queryAll() {
        if(!UserInfo.isMultiCurrencyOrganization()){
            return new List<SObject>();
        }

        return Database.query('SELECT Id, ConversionRate, IsoCode, IsCorporate FROM CurrencyType', AccessLevel.SYSTEM_MODE);
    }

    /**
    * @description Retrieve Currency Type record by Iso Code
    *
    * @param isoCode ISO Code value
    *
    * @return Currency Type record or NULL if not found
    */
    public SObject getByIsoCode(String isoCode) {
        List<SObject> currencyTypes = getByIsoCodes(new Set<String>{isoCode});
        return currencyTypes.isEmpty() ? null : currencyTypes[0];
    }

    /**
     * @description Retrieve records of currency types by ISO Codes
     *
     * @param isoCodes Iso Codes of currency types
     *
     * @return Currency Type records
     */
    public List<SObject> getByIsoCodes(Set<String> isoCodes) {
        List<SObject> currencyTypes = new List<SObject>();

        if(isoCodes == null || isoCodes.isEmpty()) {
            return currencyTypes;
        }

        for(String isoCode : isoCodes) {
            SObject currencyType = currencyIsoCodeToCurrencyType.get(isoCode);

            if(currencyType != null) {
                currencyTypes.add(currencyType);
            }
        }

        return currencyTypes;
    }

    /**
     * @description Returns corporate Currency Type
     *
     * @return CurrencyType
     */
    public SObject getCorporate() {
        return corporateCurrencyType;
    }
}
```

This is the singleton pattern:

* A private constructor plus `getInstance()` gives a lazy singleton — the first caller constructs the instance, and everyone after reuses it.
* All rows are loaded once into `currencyIsoCodeToCurrencyType` (keyed by ISO code) when the instance is constructed.
* Lookups — `getByIsoCode`, `getByIsoCodes`, and `getCorporate` — are served from memory with zero further SOQL.
* The multi-currency guard in `queryAll()` returns an empty list in single-currency orgs, where `CurrencyType` is not queryable.

Call sites get the instance and read straight from memory:

```java theme={null}
SObject usd = CurrencyTypeDB.getInstance().getByIsoCode('USD');
SObject corporate = CurrencyTypeDB.getInstance().getCorporate();
```

## Which Pattern to Use

* Use a per-object DB class that delegates to the cache (like `UserDB`) for general records queried by Id.
* Use a singleton that loads all rows (like `CurrencyTypeDB`) for small reference tables read repeatedly across a transaction.
