artigo
Service Portal e Now Experience: duas interfaces, dois públicos, uma decisão que dura anos
O ServiceNow já lançou três 'futuros' de interface em uma década — o que importa é saber para qual público cada uma foi construída.
#a terceira vez que a interface "certa" mudou
Se você acompanha ServiceNow há tempo suficiente, já viu esse filme: o Classic UI era a interface, ponto final, até 2015, quando o Service Portal chegou (release Fuji) prometendo autoatendimento decente para usuário final. Aí veio 2021 e o Now Experience apareceu com o UI Builder, prometendo a mesma coisa para quem trabalha filas e workspaces. Cada um foi anunciado como o futuro da plataforma no lançamento — e, olhando de fora, dá pra achar que a ServiceNow simplesmente não decide o que quer.
Não é indecisão. É o ritmo normal de uma plataforma corporativa que evolui junto com o resto da web: AngularJS era o padrão razoável em 2016, Web Components é o padrão razoável agora. O que muda de verdade não é a tecnologia em si — é que a interface que você constrói hoje vai durar bem mais do que o hype do lançamento, então vale entender onde a ServiceNow está investindo antes de apostar um projeto grande numa plataforma específica.
#três interfaces, três públicos, um só objetivo
A forma mais rápida de parar de confundir as três é lembrar que elas não competem — resolvem problemas de pessoas diferentes:
| Classic UI | Service Portal | Now Experience / UI Builder | |
|---|---|---|---|
| para quem | administradores e desenvolvedores | usuário final (autoatendimento, catálogo) | agentes e técnicos (filas, workspaces) |
| base técnica | Jelly, formulários clássicos | AngularJS 1.x + widgets | Web Components (padrão W3C) + low-code |
| investimento atual | mantido para administração, sem evolução de UX | mantido, correções de bug, sem grandes novidades | onde a ServiceNow está colocando o orçamento de produto |
| novo projeto? | só para configuração de plataforma | viável, com plano de migração em mente | recomendado para interfaces de agente |
O ponto de atenção fica no meio da tabela. Pesquisando a posição oficial mais recente da ServiceNow, a notícia é que o Service Portal não está sendo descontinuado — ainda tem base de clientes grande demais para isso — mas o Employee Center — que hoje ainda roda sobre o mesmo stack técnico do Service Portal, com a evolução mais recente (Employee Center Pro) já construída em cima do UI Builder/Now Experience — é o caminho recomendado hoje para portal de autoatendimento novo, com investimento trimestral contínuo. Ou seja: se você está começando um portal do zero em 2026, a pergunta não é "Service Portal ainda funciona?" (funciona), é "esse projeto vai durar o suficiente para eu preferir já nascer no caminho que está recebendo investimento?".
#widget: quatro partes, uma responsabilidade cada
Enquanto o Service Portal continua vivo — e continua, na prática, em boa parte das instâncias que você vai encontrar por aí —, vale entender onde os projetos de widget mais degringolam. Um widget tem quatro peças: HTML template, CSS, Controller (AngularJS) e Server Script. O problema não é ter quatro peças, é quando uma invade o território da outra.
O deslize mais comum: lógica de negócio dentro do Controller. Validação de regra, cálculo, decisão de "pode ou não pode" — isso é domínio do servidor, não do cliente. É exatamente a mesma separação que já vale para Business Rules: quem orquestra não deveria ser quem decide.
<!-- HTML: só apresentação, três estados possíveis -->
<div class="request-status-widget">
<div ng-if="c.loading" class="spinner"></div>
<div ng-if="!c.loading && c.error" class="alert alert-danger">{{c.error}}</div>
<div ng-if="!c.loading && !c.error">
<h4>{{c.data.short_description}}</h4>
<span class="badge" ng-class="c.badgeClass(c.data.state)">{{c.data.state_label}}</span>
<button ng-if="c.data.pode_cancelar" ng-click="c.cancelar(c.data.sys_id)">cancelar solicitação</button>
</div>
</div>/* Controller: só orquestra evento e apresentação, zero regra de negócio */
function($scope) {
var c = this;
c.error = null;
// mapa de apresentação — não é regra de negócio, é só "qual cor pra qual estado"
c.badgeClass = function(state) {
var mapa = { "1": "badge-info", "2": "badge-warning", "3": "badge-success", "-1": "badge-danger" };
return mapa[String(state)] || "badge-default";
};
c.cancelar = function(sysId) {
c.loading = true;
c.server.get({ action: "cancelar", sys_id: sysId }).then(function(r) {
if (r.data.ok) {
c.data.state = r.data.novo_estado;
c.data.state_label = r.data.novo_estado_label;
c.data.pode_cancelar = false;
} else {
c.error = r.data.erro;
}
c.loading = false;
});
};
}/* Server Script: recebe a ação, delega pro Script Include — a regra mora lá, não aqui */
(function() {
if (input && input.action === "cancelar") {
var svc = new CatalogRequestService();
data = svc.cancelarSolicitacao(input.sys_id, gs.getUserID());
return;
}
if (options.record_sys_id) {
data = new CatalogRequestService().getDetalhes(options.record_sys_id);
}
})();Repare que o Server Script em si já não tem regra nenhuma — ele recebe a intenção ("cancelar isso") e devolve o resultado de um CatalogRequestService. Se amanhã a regra de "quando pode cancelar" mudar, você edita o Script Include, não caça a lógica espalhada em três arquivos do widget.
#UI Builder: quatro conceitos que resolvem 80% das dúvidas
No Now Experience você não escreve AngularJS — monta a página com Web Components no UI Builder, low-code. A curva de aprendizado é outra, mas quatro conceitos resolvem a maior parte das perguntas de quem está começando:
| conceito | o que é | boa prática |
|---|---|---|
| Page e Variant | uma Page pode ter várias Variants — versões da mesma página por contexto ou persona | use Variant pra adaptar por role, não duplique a página inteira |
| Data Resource | fonte de dados configurável (Record, List, Scripted) | um Data Resource primário por Page; componentes filhos recebem dado via property, não criam resource próprio |
| Client State Parameter | estado local da página, sem ida ao servidor | aba ativa, filtro selecionado — não persiste entre sessões; se precisar persistir, é Record, não State Parameter |
| Actions e Events | comunicação entre componentes da mesma página | prefira evento de página a acoplamento direto entre componentes |
O padrão de erro mais comum aqui é o oposto do widget: em vez de lógica vazando pro lugar errado, é duplicação — cada componente filho criando o próprio Data Resource pra buscar o mesmo registro que o pai já tem. Além de gastar uma request HTTP a mais por componente, isso cria duas fontes de verdade pra um dado que deveria ser um só.
#performance de interface: meça antes que o usuário reclame
A percepção de lentidão do usuário não é proporcional ao tempo real — é dominada pela primeira tela e pela resposta a clique. Um formulário de 5 segundos não vira "hoje está lento", vira "o sistema é lento" — generalização que gruda.
| métrica | meta | onde medir | causa mais comum quando estoura |
|---|---|---|---|
| carregamento de formulário | < 2s | Transaction Log (campo "total") | Display BR pesado, muitos widgets simultâneos |
| paginação de lista | < 1s | tempo de resposta de query | falta de índice, ACL com script sem cache |
| busca no portal | < 3s | Transaction Log de search | campo sem Text Index |
| time to interactive do portal | < 3s | Chrome DevTools > Network | widgets em sequência, server script pesado no load |
A prática de melhor custo-benefício de todo o capítulo: pré-carregar dado via Display Business Rule + g_scratchpad, eliminando a chamada GlideAjax que normalmente acontece no onLoad. Em vez do form abrir, disparar um Client Script que pergunta pro servidor "me dá esse dado", e só então renderizar — o dado já chega dentro do form, sem round-trip extra.
Outras três que custam pouco e resolvem muito: ng-if em vez de ng-show para conteúdo pesado no Service Portal (ng-show renderiza tudo no DOM mesmo escondido, ng-if nem cria o elemento); limite prático de 8 a 10 widgets por página, porque cada um soma seu próprio processamento server-side na mesma request de renderização (e um widget marcado para carregar em block dispara, além disso, uma chamada AJAX própria); e lazy loading em lista longa, carregando mais conforme o usuário rola em vez de trazer tudo de uma vez.
#e agora?
- Se você tem um Service Portal em produção, abra dois ou três widgets ao acaso e confira se o Controller tem
ifde regra de negócio — validação, cálculo de permissão, decisão de fluxo. Se tiver, é candidato a mover pro Script Include. - Meça o Transaction Log do formulário mais usado da sua instância antes de mexer em qualquer coisa. Se já estiver abaixo de 2 segundos, ótimo — guarde o número como baseline pra próxima mudança.
- Este artigo completa a trilha de interface começada em precedência de campos entre camadas (capítulo 5, sobre Client Script e UI Policy) — os dois juntos cobrem a camada que o usuário final realmente toca, do form clássico ao portal.