/* voltgrid */ → code

VoltGrid Code

Sustainable code is layered: HTTP stays thin, business logic lives in reusable services, and every layer is testable on its own. Here a request travels through the backend layers, and on the frontend, a util, a composable and a UI primitive compose into a button without anything existing twice.

// backend, a request travels through the layers

http

The controller only translates HTTP: request in, resource out. No logic, nothing to mock.

Http/Controllers/SessionController.phpphp
final class SessionController
{
    public function store(StartSessionRequest $request, StartChargingSession $action): JsonResponse
    {
        return SessionResource::make($action->handle($request->toCommand()))
            ->response()
            ->setStatusCode(201);
    }
}
application

The action is the use case: guards with dedicated exception classes, then delegation to the domain. Testable in isolation, exactly what the test suite page exercises.

Charging/Actions/StartChargingSession.phpphp
final class StartChargingSession
{
    public function __construct(
        private readonly StationRepository $stations,
        private readonly TariffCalculator $tariff,
        private readonly EventDispatcher $events,
    ) {
    }

    public function handle(StartSessionCommand $command): ChargingSession
    {
        $station = $this->stations->findOrFail($command->stationId);

        if (! $station->supports($command->connectorType)) {
            throw new UnsupportedConnectorException($station, $command->connectorType);
        }

        if (! $station->isAvailable()) {
            throw new StationUnavailableException($station);
        }

        $estimate = $this->tariff->estimate($station, $command->estimatedKwh);
        $session = ChargingSession::start($station, $command->connectorType, $estimate);

        $this->events->dispatch(new SessionStarted($session));

        return $session;
    }
}
domain · service

The service is the single source of truth for pricing: value objects in (KilowattHours), Money out. No framework imports, pure, reusable domain logic.

Domain/Tariff/TariffCalculator.phpphp
final class TariffCalculator
{
    public function __construct(private readonly TariffRepository $tariffs)
    {
    }

    public function estimate(Station $station, KilowattHours $kwh): Money
    {
        $tariff = $this->tariffs->activeFor($station);

        return $tariff->pricePerKwh()
            ->multiply($kwh->value())
            ->add($tariff->baseFee());
    }
}

↺ reused by: StartChargingSession · FinalizeSessionInvoice · GET /stations/{id}/price-preview

queue · worker

The proof of reuse: the billing worker in the queue uses the exact same service, live estimate and invoice can never drift apart.

Billing/Jobs/FinalizeSessionInvoice.phpphp
final class FinalizeSessionInvoice implements ShouldQueue
{
    public function __construct(private readonly TariffCalculator $tariff)
    {
    }

    public function handle(SessionCompleted $event): void
    {
        // derselbe TariffCalculator wie im Live-Estimate:
        // ein Preis-Codepfad · Anzeige und Rechnung können nie abweichen
        $amount = $this->tariff->estimate($event->station, $event->chargedKwh);

        Invoice::issueFor($event->session, $amount);
    }
}

// frontend, util → composable → component

util

One util for every form: the API's 422 responses are translated into field errors in exactly one place.

utils/http.tsts
// utils/http.ts
export function extractFieldErrors(error: unknown): Record<string, string> {
  if (isFetchError(error) && error.statusCode === 422) {
    return error.data?.data?.errors ?? {}
  }

  return { _global: 'unexpected error, please retry' }
}

↺ reused by: useChargingSession · useStationFilter · useOperatorLogin

composable · logic

The composable encapsulates state as a discriminated union, impossible states cannot be represented, and any component can use it.

composables/useChargingSession.tsts
// composables/useChargingSession.ts
type SessionState =
  | { status: 'idle' }
  | { status: 'starting' }
  | { status: 'charging', session: SessionReceipt }
  | { status: 'failed', errors: Record<string, string> }

export function useChargingSession() {
  const state = ref<SessionState>({ status: 'idle' })

  async function start(payload: StartSessionPayload): Promise<void> {
    state.value = { status: 'starting' }
    try {
      const { data } = await $fetch<{ data: SessionReceipt }>('/api/v1/sessions', {
        method: 'POST',
        body: payload,
      })
      state.value = { status: 'charging', session: data }
    }
    catch (error) {
      state.value = { status: 'failed', errors: extractFieldErrors(error) }
    }
  }

  const isCharging = computed(() => state.value.status === 'charging')

  return { state: readonly(state), start, isCharging }
}
component · ui

The component only composes: logic from the composable, looks from the DgButton primitive. Seven lines, nothing duplicated.

components/ChargeButton.vuevue
<script setup lang="ts">
const props = defineProps<{ stationId: number }>()
const { state, start, isCharging } = useChargingSession()
</script>

<template>
  <DgButton
    :disabled="isCharging"
    @click="start({ stationId: props.stationId, connectorType: 'CCS', estimatedKwh: 40 })"
  >
    {{ isCharging ? '⚡ lädt …' : '→ laden' }}
  </DgButton>
</template>

↺ DgButton is the same primitive used across this whole site, one style, one place to change it

// Conventions from real projects: method parameters always type-hinted, errors as dedicated exception classes instead of generic try/catch, comments only where business logic needs explaining.